Bump Docker CLI, seccomp, and compress patches

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi committed 2026-09-30 00:28:36 -07:00
1 parent 05795897e3
commit 065bc5ed69
30 files changed
+7185 -3703

No files matched your search

+3 -3
View File
@@ -36,7 +36,7 @@ require (
github.com/containernetworking/plugins v1.9.1
github.com/coreos/go-systemd/v22 v22.7.0
github.com/distribution/reference v0.6.0
github.com/docker/cli v29.8.0+incompatible
github.com/docker/cli v29.8.1+incompatible
github.com/docker/go-units v0.5.0
github.com/gofrs/flock v0.13.1
github.com/gohugoio/hashstructure v0.6.0
@@ -47,13 +47,13 @@ require (
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/hiddeco/sshsig v0.2.0
github.com/in-toto/in-toto-golang v0.11.0
github.com/klauspost/compress v1.20.0
github.com/klauspost/compress v1.20.1
github.com/moby/docker-image-spec v1.3.1
github.com/moby/go-archive v0.2.0
github.com/moby/locker v1.0.1
github.com/moby/patternmatcher v0.6.1
github.com/moby/policy-helpers v0.0.0-20260901104222-dd6c5499c491
github.com/moby/profiles/seccomp v0.2.3
github.com/moby/profiles/seccomp v0.2.4
github.com/moby/sys/mountinfo v0.7.2
github.com/moby/sys/reexec v0.1.0
github.com/moby/sys/signal v0.7.1
+6 -6
View File
@@ -185,8 +185,8 @@ github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v29.8.0+incompatible h1:ih0c2jq/nN7QfES8zIfwSzIhRScjs4ehER+kZ60aeSk=
github.com/docker/cli v29.8.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v29.8.1+incompatible h1:qYL1bCp6cRw2SB1xmLlIOPyV171dilw9W2Jew38vy9c=
github.com/docker/cli v29.8.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker-credential-helpers v0.9.9 h1:BkydjIgZ46JnDbqyM2p2fc63KMw6y+KHL3Em/2AGJ7w=
github.com/docker/docker-credential-helpers v0.9.9/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
github.com/docker/go-metrics v0.1.0 h1:r76KPNpstz+IvQKSWpYegSkkyzex0V3A1ZGVx6bhGlY=
@@ -352,8 +352,8 @@ github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP
github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4=
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ=
github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/letsencrypt/boulder v0.20260309.0 h1:kZynrxK3QfqLGx6hhoz+Rfs3hgltJs1p9Mp+4+VwnY0=
@@ -376,8 +376,8 @@ github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x
github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/policy-helpers v0.0.0-20260901104222-dd6c5499c491 h1:5qXAyBr9AXXAqPoFK/wY85wggaKyYSMCNh8QF/O21Rk=
github.com/moby/policy-helpers v0.0.0-20260901104222-dd6c5499c491/go.mod h1:77BSYHqLljY8i6FWZhQ2V7xmgNP3eftoIRV5yHYspDs=
github.com/moby/profiles/seccomp v0.2.3 h1:nrHNSiECQQvq4WjgceCUgJXIXUJBIswVQ133k4Do2mA=
github.com/moby/profiles/seccomp v0.2.3/go.mod h1:8m3qkkWZXrRsMqlUUN2zyccnYmBf3EAdQYPMVJ3NBhk=
github.com/moby/profiles/seccomp v0.2.4 h1:CNKIR4jNYCGIdstSZ3plN/yF3WCqlupmNLwO1ckLwz4=
github.com/moby/profiles/seccomp v0.2.4/go.mod h1:H3JkVP4MWq+Q87sSWRUEdiZvK52WErUbcxr+mJHKVoM=
github.com/moby/sys/mount v0.3.5 h1:eS3fsZTjHaBihwjp4/+5Z3jxqLXYsbwxqpVSfFv3M00=
github.com/moby/sys/mount v0.3.5/go.mod h1:WUQDO+/uCiCIkIztx8SrwIDVn2dtMFRBebRhpDFT71M=
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
+30 -5
View File
@@ -95,6 +95,11 @@ type advancedState struct {
index int
hashMatch [maxMatchLength + minMatchLength]uint32
// litBits[i] is the number of bits needed to encode window[index:i]
// as literals, where index is the window index at the start of the
// current deflateLazy call. Only allocated when d.chain > 100 (level 9).
litBits []int32
// Input hash chains
// hashHead[hashValue] contains the largest inputIndex with the specified hash value
// If hashHead[hashValue] is within the current window, then
@@ -284,7 +289,9 @@ func (d *compressor) findMatch(pos int, prevHead int, lookahead int) (length, of
minIndex := max(pos-windowSize, 0)
offset = 0
if d.chain < 100 {
// The gain estimate below needs the tables that deflateLazy only
// generates when d.chain > 100, so the conditions must match.
if d.chain <= 100 {
for i := prevHead; tries > 0; tries-- {
if wEnd == win[i+length] {
n := matchLen(win[i:i+minMatchLook], wPos)
@@ -312,21 +319,25 @@ func (d *compressor) findMatch(pos int, prevHead int, lookahead int) (length, of
}
// Minimum gain to accept a match.
cGain := 4
cGain := int32(4)
// Some like it higher (CSV), some like it lower (JSON)
const baseCost = 3
// Base is 4 bytes at with an additional cost.
// Matches must be better than this.
s := d.state
hashPrev := &s.hashPrev
hashOffset := s.hashOffset
litBits := s.litBits[pos : pos+minMatchLook+1]
for i := prevHead; tries > 0; tries-- {
if wEnd == win[i+length] {
n := matchLen(win[i:i+minMatchLook], wPos)
if n > length {
// Calculate gain. Estimate
newGain := d.h.bitLengthRaw(wPos[:n]) - int(offsetExtraBits[offsetCode(uint32(pos-i))]) - baseCost - int(lengthExtraBits[lengthCodes[(n-3)&255]])
newGain := litBits[n] - litBits[0] - int32(offsetExtraBits[offsetCode(uint32(pos-i))]) - baseCost - int32(lengthExtraBits[lengthCodes[(n-3)&255]])
//fmt.Println("gain:", newGain, "prev:", cGain, "raw:", d.h.bitLengthRaw(wPos[:n]), "this-len:", n, "prev-len:", length)
if newGain > cGain {
length = n
offset = pos - i
@@ -344,7 +355,7 @@ func (d *compressor) findMatch(pos int, prevHead int, lookahead int) (length, of
// hashPrev[i & windowMask] has already been overwritten, so stop now.
break
}
i = int(d.state.hashPrev[i&windowMask]) - d.state.hashOffset
i = int(hashPrev[i&windowMask]) - hashOffset
if i < minIndex {
break
}
@@ -428,6 +439,20 @@ func (d *compressor) deflateLazy() {
tmp[v]++
}
d.h.generate(tmp[:], 15)
// Compute the cumulative cost of encoding the window as literals,
// so findMatch can look up the cost of any range in constant time.
if s.litBits == nil {
s.litBits = make([]int32, 2*windowSize+1)
}
codes := d.h.codes[:256]
litBits := s.litBits[s.index : d.windowEnd+1]
total := int32(0)
litBits[0] = 0
for i, v := range d.window[s.index:d.windowEnd] {
total += int32(codes[v].len())
litBits[i+1] = total
}
}
s.maxInsertIndex = d.windowEnd - (minMatchLength - 1)
+94 -128
View File
@@ -32,14 +32,12 @@ const (
// bufferFlushSize indicates the buffer size
// after which bytes are flushed to the writer.
// Should preferably be a multiple of 6, since
// we accumulate 6 bytes between writes to the buffer.
// Between checks, at most 7 bytes are added to the buffer,
// and writes are done as unconditional 8-byte stores, so the
// buffer must have at least bufferFlushSize+7+8 bytes.
bufferFlushSize = 246
)
// Minimum length code that emits bits.
const lengthExtraBitsMinCode = 8
// The number of extra bits needed by length code X - LENGTH_CODES_START.
var lengthExtraBits = [32]uint8{
/* 257 */ 0, 0, 0,
@@ -55,9 +53,6 @@ var lengthBase = [32]uint8{
64, 80, 96, 112, 128, 160, 192, 224, 255,
}
// Minimum offset code that emits bits.
const offsetExtraBitsMinCode = 4
// offset code word extra bits.
var offsetExtraBits = [32]int8{
0, 0, 0, 0, 1, 1, 2, 2, 3, 3,
@@ -67,8 +62,24 @@ var offsetExtraBits = [32]int8{
14, 14,
}
// offsetCombined combines the number of extra bits and the base offset
// of each offset code in a single table: extra bits in the low 8 bits,
// and the base offset (already reduced by baseMatchOffset) in the upper bits.
// Entries for the extended window codes 30 and 31 are unused.
var offsetCombined = [32]uint32{}
// lengthCombined combines, for each match length (reduced by
// baseMatchLength), the length code (relative to lengthCodesStart) in
// bits 0-4, the number of extra bits in bits 5-7 and the value of the
// extra bits in bits 8-12.
var lengthCombined = func() (t [256]uint32) {
for i := range t {
code := lengthCodes[i]
t[i] = uint32(code) | uint32(lengthExtraBits[code])<<5 | uint32(uint8(i)-lengthBase[code])<<8
}
return t
}()
func init() {
var offsetBase = [32]uint32{
/* normal deflate */
@@ -85,7 +96,7 @@ func init() {
for i := range offsetCombined[:] {
// Don't use extended window values...
if offsetExtraBits[i] == 0 || offsetBase[i] > 0x006000 {
if offsetBase[i] > 0x006000 {
continue
}
offsetCombined[i] = uint32(offsetExtraBits[i]) | (offsetBase[i] << 8)
@@ -115,10 +126,14 @@ type huffmanBitWriter struct {
lastHeader int
// Set between 0 (reused block can be up to 2x the size)
logNewTablePenalty uint
bytes [256 + 8]byte
literalFreq [lengthCodesStart + 32]uint16
offsetFreq [32]uint16
codegenFreq [codegenCodeCount]uint16
// bytes must hold at least bufferFlushSize+7+8 bytes (see bufferFlushSize).
// Its size is rounded up to a multiple of 8 to keep the following
// fields aligned, which is measurably faster in writeBlockHuff.
bytes [(bufferFlushSize + 7 + 8 + 7) &^ 7]byte
literalFreq [lengthCodesStart + 32]uint16
offsetFreq [32]uint16
codegenFreq [codegenCodeCount]uint16
// codegen must have an extra space for the final symbol.
codegen [literalCount + offsetCodeCount + 1]uint8
@@ -842,124 +857,75 @@ func (w *huffmanBitWriter) writeTokens(tokens []token, leCodes, oeCodes []hcode)
lengths := leCodes[lengthCodesStart:]
lengths = lengths[:32]
// Go 1.16 LOVES having these on stack.
// Keeping these on the stack instead of in w is significantly faster.
bits, nbits, nbytes := w.bits, w.nbits, w.nbytes
for _, t := range tokens {
// Flush whole bytes, so that nbits <= 7 when entering the loop below.
le.Store64(w.bytes[:], nbytes, bits)
nbytes += nbits >> 3
bits >>= nbits & 56
nbits &= 7
if nbytes >= bufferFlushSize {
_, w.err = w.writer.Write(w.bytes[:nbytes])
nbytes = 0
if w.err != nil {
return
}
}
// The loop below flushes whole bytes at the end of every iteration,
// so that nbits <= 7 at the top of every iteration.
// A literal adds at most 15 bits and a match at most
// 15+5+15+13 = 48 bits, so up to three literals or a single match
// can be added to the 64-bit accumulator without an intermediate flush.
// Unconditionally storing 8 bytes and advancing by the number of
// whole bytes avoids a poorly predicted branch per token.
for i := 0; i < len(tokens); i++ {
t := tokens[i]
if t < 256 {
//w.writeCode(lits[t.literal()])
c := lits[t]
bits |= c.code64() << (nbits & 63)
nbits += c.len()
if nbits >= 48 {
le.Store64(w.bytes[:], nbytes, bits)
bits >>= 48
nbits -= 48
nbytes += 6
if nbytes >= bufferFlushSize {
if w.err != nil {
nbytes = 0
return
}
_, w.err = w.writer.Write(w.bytes[:nbytes])
nbytes = 0
// Add up to two more literals before flushing.
if i+1 < len(tokens) && tokens[i+1] < 256 {
i++
c := lits[tokens[i]]
bits |= c.code64() << (nbits & 63)
nbits += c.len()
if i+1 < len(tokens) && tokens[i+1] < 256 {
i++
c := lits[tokens[i]]
bits |= c.code64() << (nbits & 63)
nbits += c.len()
}
}
continue
}
// Write the length
length := t.length()
lengthCode := lengthCode(length) & 31
if false {
w.writeCode(lengths[lengthCode])
} else {
// inlined
c := lengths[lengthCode]
bits |= c.code64() << (nbits & 63)
nbits += c.len()
if nbits >= 48 {
le.Store64(w.bytes[:], nbytes, bits)
bits >>= 48
nbits -= 48
nbytes += 6
if nbytes >= bufferFlushSize {
if w.err != nil {
nbytes = 0
return
}
_, w.err = w.writer.Write(w.bytes[:nbytes])
nbytes = 0
}
}
}
// Write the length code and its extra bits as one unit.
lc := lengthCombined[t.length()]
c := lengths[lc&31]
bits |= (c.code64() | uint64(lc>>8)<<(c.len()&63)) << (nbits & 63)
nbits += c.len() + uint8(lc>>5)&7
if lengthCode >= lengthExtraBitsMinCode {
extraLengthBits := lengthExtraBits[lengthCode]
//w.writeBits(extraLength, extraLengthBits)
extraLength := int32(length - lengthBase[lengthCode])
bits |= uint64(extraLength) << (nbits & 63)
nbits += extraLengthBits
if nbits >= 48 {
le.Store64(w.bytes[:], nbytes, bits)
bits >>= 48
nbits -= 48
nbytes += 6
if nbytes >= bufferFlushSize {
if w.err != nil {
nbytes = 0
return
}
_, w.err = w.writer.Write(w.bytes[:nbytes])
nbytes = 0
}
}
// Write the offset code and its extra bits as one unit.
offset := t.offset()
offCode := (offset >> 16) & 31
c = offs[offCode]
offComb := offsetCombined[offCode]
extra := (offset - (offComb >> 8)) & matchOffsetOnlyMask
bits |= (c.code64() | uint64(extra)<<(c.len()&63)) << (nbits & 63)
nbits += c.len() + uint8(offComb)
}
// Write the offset
offset := t.offset()
offsetCode := (offset >> 16) & 31
if false {
w.writeCode(offs[offsetCode])
} else {
// inlined
c := offs[offsetCode]
bits |= c.code64() << (nbits & 63)
nbits += c.len()
if nbits >= 48 {
le.Store64(w.bytes[:], nbytes, bits)
bits >>= 48
nbits -= 48
nbytes += 6
if nbytes >= bufferFlushSize {
if w.err != nil {
nbytes = 0
return
}
_, w.err = w.writer.Write(w.bytes[:nbytes])
nbytes = 0
}
}
}
if offsetCode >= offsetExtraBitsMinCode {
offsetComb := offsetCombined[offsetCode]
//w.writeBits(extraOffset, extraOffsetBits)
bits |= uint64((offset-(offsetComb>>8))&matchOffsetOnlyMask) << (nbits & 63)
nbits += uint8(offsetComb)
if nbits >= 48 {
le.Store64(w.bytes[:], nbytes, bits)
bits >>= 48
nbits -= 48
nbytes += 6
if nbytes >= bufferFlushSize {
if w.err != nil {
nbytes = 0
return
}
_, w.err = w.writer.Write(w.bytes[:nbytes])
nbytes = 0
}
le.Store64(w.bytes[:], nbytes, bits)
nbytes += nbits >> 3
bits >>= nbits & 56
nbits &= 7
if nbytes >= bufferFlushSize {
if w.err != nil {
nbytes = 0
return
}
_, w.err = w.writer.Write(w.bytes[:nbytes])
nbytes = 0
}
}
// Restore...
@@ -1094,8 +1060,8 @@ func (w *huffmanBitWriter) writeBlockHuff(eof bool, input []byte, sync bool) {
if debugDeflate {
count -= int(nbytes)*8 + int(nbits)
}
// Unroll, write 3 codes/loop.
// Fastest number of unrolls.
// Write 3 codes per iteration. The three codes are combined first,
// independently of the accumulator, and merged with a single shift.
for len(input) > 3 {
// We must have at least 48 bits free.
if nbits >= 8 {
@@ -1116,13 +1082,13 @@ func (w *huffmanBitWriter) writeBlockHuff(eof bool, input []byte, sync bool) {
_, w.err = w.writer.Write(w.bytes[:nbytes])
nbytes = 0
}
a, b := encoding[input[0]], encoding[input[1]]
bits |= a.code64() << (nbits & 63)
bits |= b.code64() << ((nbits + a.len()) & 63)
c := encoding[input[2]]
nbits += b.len() + a.len()
bits |= c.code64() << (nbits & 63)
nbits += c.len()
a, b, c := encoding[input[0]], encoding[input[1]], encoding[input[2]]
v := a.code64() | b.code64()<<(a.len()&63)
n := a.len() + b.len()
v |= c.code64() << (n & 63)
n += c.len()
bits |= v << (nbits & 63)
nbits += n
input = input[3:]
}
+102 -36
View File
@@ -38,6 +38,9 @@ type huffmanEncoder struct {
// Possible lengths are codegenCodeCount, offsetCodeCount and literalCount.
// The largest of these is literalCount, so we allocate for that case.
freqcache [literalCount + 1]literalNode
// sortcache is scratch space for sortByFreq.
sortcache [literalCount + 1]literalNode
}
type literalNode struct {
@@ -139,14 +142,6 @@ func (h *huffmanEncoder) bitLength(freq []uint16) int {
return total
}
func (h *huffmanEncoder) bitLengthRaw(b []byte) int {
var total int
for _, f := range b {
total += int(h.codes[f].len())
}
return total
}
// canReuseBits returns the number of bits or math.MaxInt32 if the encoder cannot be reused.
func (h *huffmanEncoder) canReuseBits(freq []uint16) int {
var total int
@@ -309,27 +304,93 @@ func (h *huffmanEncoder) bitCounts(list []literalNode, maxBits int32) []int32 {
}
// Look at the leaves and assign them a bit count and an encoding as specified
// in RFC 1951 3.2.2
func (h *huffmanEncoder) assignEncodingAndSize(bitCount []int32, list []literalNode) {
code := uint16(0)
// in RFC 1951 3.2.2.
// list must be sorted by increasing frequency and codes must have
// all entries for literals not in list set to zero.
func (h *huffmanEncoder) assignEncodingAndSize(bitCount []int32, list []literalNode, codes []hcode) {
// The literals list[len(list)-bits] .. list[len(list)-1]
// are encoded using "bits" bits. Record the length of each literal
// temporarily in codes.
for n, bits := range bitCount {
code <<= 1
if n == 0 || bits == 0 {
continue
}
// The literals list[len(list)-bits] .. list[len(list)-bits]
// are encoded using "bits" bits, and get the values
// code, code + 1, .... The code values are
// assigned in literal order (not frequency order).
chunk := list[len(list)-int(bits):]
sortByLiteral(chunk)
for _, node := range chunk {
h.codes[node.literal] = newhcode(reverseBits(code, uint8(n)), uint8(n))
code++
for _, node := range list[len(list)-int(bits):] {
codes[node.literal] = hcode(n)
}
list = list[0 : len(list)-int(bits)]
}
// Code values are assigned in literal order (not frequency order)
// within each code length, starting from the first code of each length.
var nextCode [maxBitsLimit]uint16
code := uint16(0)
for n := 1; n < len(bitCount); n++ {
code = (code + uint16(bitCount[n-1])) << 1
nextCode[n] = code
}
for i, c := range codes {
if c == 0 {
continue
}
// n is at most maxBitsLimit-1; the mask only elides bounds checks.
n := uint8(c)
codes[i] = newhcode(reverseBits(nextCode[n&15], n), n)
nextCode[n&15]++
}
}
// sortByFreq sorts list by increasing frequency.
// The sort is stable, so nodes with equal frequencies keep their order.
func (h *huffmanEncoder) sortByFreq(list []literalNode) {
if len(list) <= 32 {
// Insertion sort is faster for small lists.
for i := 1; i < len(list); i++ {
n := list[i]
j := i
for ; j > 0 && list[j-1].freq > n.freq; j-- {
list[j] = list[j-1]
}
list[j] = n
}
return
}
// Counting sort on the low byte and then on the high byte
// of the frequency. Both passes are stable.
tmp := h.sortcache[:len(list)]
var count [256]uint16
var maxFreq uint16
for _, n := range list {
count[n.freq&0xff]++
maxFreq |= n.freq
}
var pos uint16
for i, c := range count {
count[i] = pos
pos += c
}
for _, n := range list {
tmp[count[n.freq&0xff]] = n
count[n.freq&0xff]++
}
if maxFreq < 256 {
copy(list, tmp)
return
}
count = [256]uint16{}
for _, n := range tmp {
count[n.freq>>8]++
}
pos = 0
for i, c := range count {
count[i] = pos
pos += c
}
for _, n := range tmp {
list[count[n.freq>>8]] = n
count[n.freq>>8]++
}
}
// Update this Huffman Code object to be the minimum code for the specified frequency count.
@@ -362,12 +423,14 @@ func (h *huffmanEncoder) generate(freq []uint16, maxBits int32) {
}
return
}
sortByFreq(list)
// list is in order of increasing literal value, so a stable sort
// by frequency orders it by (frequency, literal).
h.sortByFreq(list)
// Get the number of literals for each bit count
bitCount := h.bitCounts(list, maxBits)
// And do the assignment
h.assignEncodingAndSize(bitCount, list)
h.assignEncodingAndSize(bitCount, list, codes)
}
// atLeastOne clamps the result between 1 and 15.
@@ -394,24 +457,27 @@ func histogram(b []byte, h []uint16) {
}
func histogramSplit(b []byte, h []uint16) {
// Tested, and slightly faster than 2-way.
// Writing to separate arrays and combining is also slightly slower.
h = h[:256]
// Walk four quarters in parallel, each into its own table.
// Tested to be faster than walking halves. Using separate tables
// avoids the load/store dependencies that arise when two of the
// streams hit the same byte value, which is common for text.
var h0, h1, h2, h3 [256]uint16
// Make size divisible by 4
for len(b)&3 != 0 {
h[b[0]]++
h0[b[0]]++
b = b[1:]
}
n := len(b) / 4
x, y, z, w := b[:n], b[n:], b[n+n:], b[n+n+n:]
y, z, w = y[:len(x)], z[:len(x)], w[:len(x)]
for i, t := range x {
v0 := &h[t]
v1 := &h[y[i]]
v2 := &h[z[i]]
v3 := &h[w[i]]
*v0++
*v1++
*v2++
*v3++
h0[t]++
h1[y[i]]++
h2[z[i]]++
h3[w[i]]++
}
h = h[:256]
for i := range h {
h[i] += h0[i] + h1[i] + h2[i] + h3[i]
}
}
-159
View File
@@ -1,159 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package flate
// Sort sorts data.
// It makes one call to data.Len to determine n, and O(n*log(n)) calls to
// data.Less and data.Swap. The sort is not guaranteed to be stable.
func sortByFreq(data []literalNode) {
n := len(data)
quickSortByFreq(data, 0, n, maxDepth(n))
}
func quickSortByFreq(data []literalNode, a, b, maxDepth int) {
for b-a > 12 { // Use ShellSort for slices <= 12 elements
if maxDepth == 0 {
heapSort(data, a, b)
return
}
maxDepth--
mlo, mhi := doPivotByFreq(data, a, b)
// Avoiding recursion on the larger subproblem guarantees
// a stack depth of at most lg(b-a).
if mlo-a < b-mhi {
quickSortByFreq(data, a, mlo, maxDepth)
a = mhi // i.e., quickSortByFreq(data, mhi, b)
} else {
quickSortByFreq(data, mhi, b, maxDepth)
b = mlo // i.e., quickSortByFreq(data, a, mlo)
}
}
if b-a > 1 {
// Do ShellSort pass with gap 6
// It could be written in this simplified form cause b-a <= 12
for i := a + 6; i < b; i++ {
if data[i].freq == data[i-6].freq && data[i].literal < data[i-6].literal || data[i].freq < data[i-6].freq {
data[i], data[i-6] = data[i-6], data[i]
}
}
insertionSortByFreq(data, a, b)
}
}
func doPivotByFreq(data []literalNode, lo, hi int) (midlo, midhi int) {
m := int(uint(lo+hi) >> 1) // Written like this to avoid integer overflow.
if hi-lo > 40 {
// Tukey's ``Ninther,'' median of three medians of three.
s := (hi - lo) / 8
medianOfThreeSortByFreq(data, lo, lo+s, lo+2*s)
medianOfThreeSortByFreq(data, m, m-s, m+s)
medianOfThreeSortByFreq(data, hi-1, hi-1-s, hi-1-2*s)
}
medianOfThreeSortByFreq(data, lo, m, hi-1)
// Invariants are:
// data[lo] = pivot (set up by ChoosePivot)
// data[lo < i < a] < pivot
// data[a <= i < b] <= pivot
// data[b <= i < c] unexamined
// data[c <= i < hi-1] > pivot
// data[hi-1] >= pivot
pivot := lo
a, c := lo+1, hi-1
for ; a < c && (data[a].freq == data[pivot].freq && data[a].literal < data[pivot].literal || data[a].freq < data[pivot].freq); a++ {
}
b := a
for {
for ; b < c && (data[pivot].freq == data[b].freq && data[pivot].literal > data[b].literal || data[pivot].freq > data[b].freq); b++ { // data[b] <= pivot
}
for ; b < c && (data[pivot].freq == data[c-1].freq && data[pivot].literal < data[c-1].literal || data[pivot].freq < data[c-1].freq); c-- { // data[c-1] > pivot
}
if b >= c {
break
}
// data[b] > pivot; data[c-1] <= pivot
data[b], data[c-1] = data[c-1], data[b]
b++
c--
}
// If hi-c<3 then there are duplicates (by property of median of nine).
// Let's be a bit more conservative, and set border to 5.
protect := hi-c < 5
if !protect && hi-c < (hi-lo)/4 {
// Lets test some points for equality to pivot
dups := 0
if data[pivot].freq == data[hi-1].freq && data[pivot].literal > data[hi-1].literal || data[pivot].freq > data[hi-1].freq { // data[hi-1] = pivot
data[c], data[hi-1] = data[hi-1], data[c]
c++
dups++
}
if data[b-1].freq == data[pivot].freq && data[b-1].literal > data[pivot].literal || data[b-1].freq > data[pivot].freq { // data[b-1] = pivot
b--
dups++
}
// m-lo = (hi-lo)/2 > 6
// b-lo > (hi-lo)*3/4-1 > 8
// ==> m < b ==> data[m] <= pivot
if data[m].freq == data[pivot].freq && data[m].literal > data[pivot].literal || data[m].freq > data[pivot].freq { // data[m] = pivot
data[m], data[b-1] = data[b-1], data[m]
b--
dups++
}
// if at least 2 points are equal to pivot, assume skewed distribution
protect = dups > 1
}
if protect {
// Protect against a lot of duplicates
// Add invariant:
// data[a <= i < b] unexamined
// data[b <= i < c] = pivot
for {
for ; a < b && (data[b-1].freq == data[pivot].freq && data[b-1].literal > data[pivot].literal || data[b-1].freq > data[pivot].freq); b-- { // data[b] == pivot
}
for ; a < b && (data[a].freq == data[pivot].freq && data[a].literal < data[pivot].literal || data[a].freq < data[pivot].freq); a++ { // data[a] < pivot
}
if a >= b {
break
}
// data[a] == pivot; data[b-1] < pivot
data[a], data[b-1] = data[b-1], data[a]
a++
b--
}
}
// Swap pivot into middle
data[pivot], data[b-1] = data[b-1], data[pivot]
return b - 1, c
}
// Insertion sort
func insertionSortByFreq(data []literalNode, a, b int) {
for i := a + 1; i < b; i++ {
for j := i; j > a && (data[j].freq == data[j-1].freq && data[j].literal < data[j-1].literal || data[j].freq < data[j-1].freq); j-- {
data[j], data[j-1] = data[j-1], data[j]
}
}
}
// quickSortByFreq, loosely following Bentley and McIlroy,
// ``Engineering a Sort Function,'' SP&E November 1993.
// medianOfThreeSortByFreq moves the median of the three values data[m0], data[m1], data[m2] into data[m1].
func medianOfThreeSortByFreq(data []literalNode, m1, m0, m2 int) {
// sort 3 elements
if data[m1].freq == data[m0].freq && data[m1].literal < data[m0].literal || data[m1].freq < data[m0].freq {
data[m1], data[m0] = data[m0], data[m1]
}
// data[m0] <= data[m1]
if data[m2].freq == data[m1].freq && data[m2].literal < data[m1].literal || data[m2].freq < data[m1].freq {
data[m2], data[m1] = data[m1], data[m2]
// data[m0] <= data[m2] && data[m1] < data[m2]
if data[m1].freq == data[m0].freq && data[m1].literal < data[m0].literal || data[m1].freq < data[m0].freq {
data[m1], data[m0] = data[m0], data[m1]
}
}
// now data[m0] <= data[m1] <= data[m2]
}
-201
View File
@@ -1,201 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package flate
// Sort sorts data.
// It makes one call to data.Len to determine n, and O(n*log(n)) calls to
// data.Less and data.Swap. The sort is not guaranteed to be stable.
func sortByLiteral(data []literalNode) {
n := len(data)
quickSort(data, 0, n, maxDepth(n))
}
func quickSort(data []literalNode, a, b, maxDepth int) {
for b-a > 12 { // Use ShellSort for slices <= 12 elements
if maxDepth == 0 {
heapSort(data, a, b)
return
}
maxDepth--
mlo, mhi := doPivot(data, a, b)
// Avoiding recursion on the larger subproblem guarantees
// a stack depth of at most lg(b-a).
if mlo-a < b-mhi {
quickSort(data, a, mlo, maxDepth)
a = mhi // i.e., quickSort(data, mhi, b)
} else {
quickSort(data, mhi, b, maxDepth)
b = mlo // i.e., quickSort(data, a, mlo)
}
}
if b-a > 1 {
// Do ShellSort pass with gap 6
// It could be written in this simplified form cause b-a <= 12
for i := a + 6; i < b; i++ {
if data[i].literal < data[i-6].literal {
data[i], data[i-6] = data[i-6], data[i]
}
}
insertionSort(data, a, b)
}
}
func heapSort(data []literalNode, a, b int) {
first := a
lo := 0
hi := b - a
// Build heap with greatest element at top.
for i := (hi - 1) / 2; i >= 0; i-- {
siftDown(data, i, hi, first)
}
// Pop elements, largest first, into end of data.
for i := hi - 1; i >= 0; i-- {
data[first], data[first+i] = data[first+i], data[first]
siftDown(data, lo, i, first)
}
}
// siftDown implements the heap property on data[lo, hi).
// first is an offset into the array where the root of the heap lies.
func siftDown(data []literalNode, lo, hi, first int) {
root := lo
for {
child := 2*root + 1
if child >= hi {
break
}
if child+1 < hi && data[first+child].literal < data[first+child+1].literal {
child++
}
if data[first+root].literal > data[first+child].literal {
return
}
data[first+root], data[first+child] = data[first+child], data[first+root]
root = child
}
}
func doPivot(data []literalNode, lo, hi int) (midlo, midhi int) {
m := int(uint(lo+hi) >> 1) // Written like this to avoid integer overflow.
if hi-lo > 40 {
// Tukey's ``Ninther,'' median of three medians of three.
s := (hi - lo) / 8
medianOfThree(data, lo, lo+s, lo+2*s)
medianOfThree(data, m, m-s, m+s)
medianOfThree(data, hi-1, hi-1-s, hi-1-2*s)
}
medianOfThree(data, lo, m, hi-1)
// Invariants are:
// data[lo] = pivot (set up by ChoosePivot)
// data[lo < i < a] < pivot
// data[a <= i < b] <= pivot
// data[b <= i < c] unexamined
// data[c <= i < hi-1] > pivot
// data[hi-1] >= pivot
pivot := lo
a, c := lo+1, hi-1
for ; a < c && data[a].literal < data[pivot].literal; a++ {
}
b := a
for {
for ; b < c && data[pivot].literal > data[b].literal; b++ { // data[b] <= pivot
}
for ; b < c && data[pivot].literal < data[c-1].literal; c-- { // data[c-1] > pivot
}
if b >= c {
break
}
// data[b] > pivot; data[c-1] <= pivot
data[b], data[c-1] = data[c-1], data[b]
b++
c--
}
// If hi-c<3 then there are duplicates (by property of median of nine).
// Let's be a bit more conservative, and set border to 5.
protect := hi-c < 5
if !protect && hi-c < (hi-lo)/4 {
// Lets test some points for equality to pivot
dups := 0
if data[pivot].literal > data[hi-1].literal { // data[hi-1] = pivot
data[c], data[hi-1] = data[hi-1], data[c]
c++
dups++
}
if data[b-1].literal > data[pivot].literal { // data[b-1] = pivot
b--
dups++
}
// m-lo = (hi-lo)/2 > 6
// b-lo > (hi-lo)*3/4-1 > 8
// ==> m < b ==> data[m] <= pivot
if data[m].literal > data[pivot].literal { // data[m] = pivot
data[m], data[b-1] = data[b-1], data[m]
b--
dups++
}
// if at least 2 points are equal to pivot, assume skewed distribution
protect = dups > 1
}
if protect {
// Protect against a lot of duplicates
// Add invariant:
// data[a <= i < b] unexamined
// data[b <= i < c] = pivot
for {
for ; a < b && data[b-1].literal > data[pivot].literal; b-- { // data[b] == pivot
}
for ; a < b && data[a].literal < data[pivot].literal; a++ { // data[a] < pivot
}
if a >= b {
break
}
// data[a] == pivot; data[b-1] < pivot
data[a], data[b-1] = data[b-1], data[a]
a++
b--
}
}
// Swap pivot into middle
data[pivot], data[b-1] = data[b-1], data[pivot]
return b - 1, c
}
// Insertion sort
func insertionSort(data []literalNode, a, b int) {
for i := a + 1; i < b; i++ {
for j := i; j > a && data[j].literal < data[j-1].literal; j-- {
data[j], data[j-1] = data[j-1], data[j]
}
}
}
// maxDepth returns a threshold at which quicksort should switch
// to heapsort. It returns 2*ceil(lg(n+1)).
func maxDepth(n int) int {
var depth int
for i := n; i > 0; i >>= 1 {
depth++
}
return depth * 2
}
// medianOfThree moves the median of the three values data[m0], data[m1], data[m2] into data[m1].
func medianOfThree(data []literalNode, m1, m0, m2 int) {
// sort 3 elements
if data[m1].literal < data[m0].literal {
data[m1], data[m0] = data[m0], data[m1]
}
// data[m0] <= data[m1]
if data[m2].literal < data[m1].literal {
data[m2], data[m1] = data[m1], data[m2]
// data[m0] <= data[m2] && data[m1] < data[m2]
if data[m1].literal < data[m0].literal {
data[m1], data[m0] = data[m0], data[m1]
}
}
// now data[m0] <= data[m1] <= data[m2]
}
+15 -15
View File
@@ -120,7 +120,7 @@ readLiteral:
if debugDecode {
fmt.Println("morebits n>0:", err)
}
f.err = err
f.err = noEOF(err)
return
}
f.roffset++
@@ -148,7 +148,7 @@ readLiteral:
if debugDecode {
fmt.Println("morebits f.nb<5:", err)
}
f.err = err
f.err = noEOF(err)
return
}
f.roffset++
@@ -216,7 +216,7 @@ readLiteral:
if debugDecode {
fmt.Println("morebits f.nb<nb:", err)
}
f.err = err
f.err = noEOF(err)
return
}
f.roffset++
@@ -371,7 +371,7 @@ readLiteral:
if debugDecode {
fmt.Println("morebits n>0:", err)
}
f.err = err
f.err = noEOF(err)
return
}
f.roffset++
@@ -399,7 +399,7 @@ readLiteral:
if debugDecode {
fmt.Println("morebits f.nb<5:", err)
}
f.err = err
f.err = noEOF(err)
return
}
f.roffset++
@@ -467,7 +467,7 @@ readLiteral:
if debugDecode {
fmt.Println("morebits f.nb<nb:", err)
}
f.err = err
f.err = noEOF(err)
return
}
f.roffset++
@@ -638,7 +638,7 @@ readLiteral:
if debugDecode {
fmt.Println("morebits n>0:", err)
}
f.err = err
f.err = noEOF(err)
return
}
}
@@ -674,7 +674,7 @@ readLiteral:
if debugDecode {
fmt.Println("morebits f.nb<5:", err)
}
f.err = err
f.err = noEOF(err)
return
}
}
@@ -757,7 +757,7 @@ readLiteral:
if debugDecode {
fmt.Println("morebits f.nb<nb:", err)
}
f.err = err
f.err = noEOF(err)
return
}
}
@@ -918,7 +918,7 @@ readLiteral:
if debugDecode {
fmt.Println("morebits n>0:", err)
}
f.err = err
f.err = noEOF(err)
return
}
f.roffset++
@@ -946,7 +946,7 @@ readLiteral:
if debugDecode {
fmt.Println("morebits f.nb<5:", err)
}
f.err = err
f.err = noEOF(err)
return
}
f.roffset++
@@ -1014,7 +1014,7 @@ readLiteral:
if debugDecode {
fmt.Println("morebits f.nb<nb:", err)
}
f.err = err
f.err = noEOF(err)
return
}
f.roffset++
@@ -1169,7 +1169,7 @@ readLiteral:
if debugDecode {
fmt.Println("morebits n>0:", err)
}
f.err = err
f.err = noEOF(err)
return
}
f.roffset++
@@ -1197,7 +1197,7 @@ readLiteral:
if debugDecode {
fmt.Println("morebits f.nb<5:", err)
}
f.err = err
f.err = noEOF(err)
return
}
f.roffset++
@@ -1265,7 +1265,7 @@ readLiteral:
if debugDecode {
fmt.Println("morebits f.nb<nb:", err)
}
f.err = err
f.err = noEOF(err)
return
}
f.roffset++
+6 -7
View File
@@ -12,18 +12,17 @@ import (
// matchLen returns the maximum common prefix length of a and b.
// a must be the shortest of the two.
func matchLen(a, b []byte) (n int) {
left := len(a)
for left >= 8 {
diff := le.Load64(a, n) ^ le.Load64(b, n)
// Keeping a and b the same length and reslicing them as we go
// lets the compiler prove that no bounds checks are needed.
b = b[:len(a)]
for len(a) >= 8 {
diff := le.Load64(a, 0) ^ le.Load64(b, 0)
if diff != 0 {
return n + bits.TrailingZeros64(diff)>>3
}
a, b = a[8:], b[8:]
n += 8
left -= 8
}
a = a[n:]
b = b[n:]
for i := range a {
if a[i] != b[i] {
break
+11 -7
View File
@@ -215,7 +215,11 @@ func mFastLog2(val float32) float32 {
ux &= -0x7f800001
ux += 127 << 23
uval := math.Float32frombits(uint32(ux))
log2 += ((-0.34484843)*uval+2.02466578)*uval - 0.67487759
p := float32(-0.34484843*uval) + 2.02466578
q := float32(p*uval) - 0.67487759
log2 += q
return log2
}
@@ -232,7 +236,8 @@ func (t *tokens) EstimatedBits() int {
for _, v := range t.litHist[:] {
if v > 0 {
n := float32(v)
shannon += atLeastOne(-mFastLog2(n*invTotal)) * n
w := float32(atLeastOne(-mFastLog2(n*invTotal)) * n)
shannon += w
}
}
// Just add 15 for EOB
@@ -240,7 +245,8 @@ func (t *tokens) EstimatedBits() int {
for i, v := range t.extraHist[1 : literalCount-256] {
if v > 0 {
n := float32(v)
shannon += atLeastOne(-mFastLog2(n*invTotal)) * n
w := float32(atLeastOne(-mFastLog2(n*invTotal)) * n)
shannon += w
bits += int(lengthExtraBits[i&31]) * int(v)
nMatches += int(v)
}
@@ -251,7 +257,8 @@ func (t *tokens) EstimatedBits() int {
for i, v := range t.offHist[:offsetCodeCount] {
if v > 0 {
n := float32(v)
shannon += atLeastOne(-mFastLog2(n*invTotal)) * n
w := float32(atLeastOne(-mFastLog2(n*invTotal)) * n)
shannon += w
bits += int(offsetExtraBits[i&31]) * int(v)
}
}
@@ -358,9 +365,6 @@ func (t token) offset() uint32 { return uint32(t) & offsetMask }
func (t token) length() uint8 { return uint8(t >> lengthShift) }
// Convert length to code.
func lengthCode(len uint8) uint8 { return lengthCodes[len] }
// Returns the offset code corresponding to a specific offset
func offsetCode(off uint32) uint32 {
if false {
+63
View File
@@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"io"
"math/bits"
"github.com/klauspost/compress/internal/le"
)
@@ -210,6 +211,68 @@ func (b *bitReaderShifted) remaining() uint {
return b.off*8 + uint(64-b.bitsRead)
}
// canUseAsm reports whether the reader has a full 8-byte window ahead of
// its read pointer, which the Decompress4X asm loops need to take over.
func (b *bitReaderShifted) canUseAsm() bool {
return b.off >= 8
}
// prepareForAsm establishes the invariant the Decompress4X asm loops rely
// on: value == load64(in[off:off+8]) << bitsRead with bitsRead <= 7, so
// that a full group of symbols can never shift their sentinel bit out of
// the container. init leaves bitsRead == 8 when the final byte of the
// stream is exactly 0x01; that whole byte is consumed, so the same position
// is the window one byte lower with nothing consumed. Requires canUseAsm.
func (b *bitReaderShifted) prepareForAsm() {
if b.bitsRead >= 8 {
b.off--
b.value = le.Load64(b.in, b.off)
b.bitsRead -= 8
}
}
// restoreFromAsm converts the state left behind by the Decompress4X asm
// loops back to the invariant the Go code relies on: value holds the 8
// bytes at in[off:off+8] shifted left by bitsRead, with the low bitsRead
// bits zero.
//
// The asm keeps a sentinel bit in value just below the unread bits, so its
// trailing zero count is the number of consumed bits. The sentinel is ORed
// over the lowest bit of the window, which the loop never consumes before
// re-reading memory, so the window is re-read here too rather than taken
// from value. The asm reports off as the signed distance from the start of
// the stream, and it can be negative: a reload always reads a whole 8-byte
// window, so a stream that is nearly drained ends with up to 7 bytes of the
// previous stream (or the jump table) below its start inside the window.
// Those bytes sit below the stream's own bits and count as consumed.
// Anything further below, or more bits consumed than the stream holds, is
// corruption.
func (b *bitReaderShifted) restoreFromAsm() error {
off := int(b.off)
consumed := uint(bits.TrailingZeros64(b.value))
if off < 0 {
if off < -7 {
return errors.New("corruption detected: stream underrun")
}
consumed += uint(-off) * 8
if consumed > 64 {
return errors.New("corruption detected: stream underrun")
}
off = 0
}
if off+8 > len(b.in) {
return errors.New("corruption detected: stream overrun")
}
b.off = uint(off)
b.bitsRead = uint8(consumed)
if consumed >= 64 {
b.value = 0
} else {
b.value = le.Load64(b.in, b.off) << consumed
}
return nil
}
// close the bitstream and returns an error if out-of-buffer reads occurred.
func (b *bitReaderShifted) close() error {
// Release reference.
+87 -8
View File
@@ -8,36 +8,99 @@ import (
)
// decompress4x_main_loop_amd64 is an x86 assembler implementation
// of Decompress4X when tablelog > 8.
// of Decompress4X when tablelog > 8, decoding fastSymbols symbols per
// stream between bit container reloads.
//
//go:noescape
func decompress4x_main_loop_amd64(ctx *decompress4xContext)
// decompress4x_8b_main_loop_amd64 is an x86 assembler implementation
// of Decompress4X when tablelog <= 8 which decodes 4 entries
// per loop.
// of Decompress4X when tablelog <= 8, decoding fast8bSymbols symbols
// per stream between bit container reloads.
//
//go:noescape
func decompress4x_8b_main_loop_amd64(ctx *decompress4xContext)
// decompress4x_4b_main_loop_amd64 is an x86 assembler implementation
// of Decompress4X when tablelog <= 4, decoding fast4bSymbols symbols
// per stream between bit container reloads.
//
//go:noescape
func decompress4x_4b_main_loop_amd64(ctx *decompress4xContext)
// decompress4x_4b_main_loop_bmi2 is the BMI2 twin of decompress4x_4b_main_loop_amd64.
//
//go:noescape
func decompress4x_4b_main_loop_bmi2(ctx *decompress4xContext)
// decompress4x_main_loop_bmi2 is the BMI2 twin of decompress4x_main_loop_amd64.
//
//go:noescape
func decompress4x_main_loop_bmi2(ctx *decompress4xContext)
// decompress4x_8b_main_loop_bmi2 is the BMI2 twin of decompress4x_8b_main_loop_amd64.
//
//go:noescape
func decompress4x_8b_main_loop_bmi2(ctx *decompress4xContext)
// decompress1x_main_loop_amd64 is an x86 assembler implementation
// of Decompress1X when tablelog > 8.
// of Decompress1X when tablelog > 8, decoding fastSymbols symbols
// between bit container reloads.
//
//go:noescape
func decompress1x_main_loop_amd64(ctx *decompress1xContext)
// decompress1x_main_loop_bmi2 is an x86 with BMI2 assembler implementation
// of Decompress1X when tablelog > 8.
// decompress1x_8b_main_loop_amd64 is an x86 assembler implementation
// of Decompress1X when tablelog <= 8, decoding fast8bSymbols symbols
// between bit container reloads.
//
//go:noescape
func decompress1x_8b_main_loop_amd64(ctx *decompress1xContext)
// decompress1x_4b_main_loop_amd64 is an x86 assembler implementation
// of Decompress1X when tablelog <= 4, decoding fast4bSymbols symbols
// between bit container reloads.
//
//go:noescape
func decompress1x_4b_main_loop_amd64(ctx *decompress1xContext)
// decompress1x_main_loop_bmi2 is the BMI2 twin of decompress1x_main_loop_amd64.
//
//go:noescape
func decompress1x_main_loop_bmi2(ctx *decompress1xContext)
// decompress1x_8b_main_loop_bmi2 is the BMI2 twin of decompress1x_8b_main_loop_amd64.
//
//go:noescape
func decompress1x_8b_main_loop_bmi2(ctx *decompress1xContext)
// decompress1x_4b_main_loop_bmi2 is the BMI2 twin of decompress1x_4b_main_loop_amd64.
//
//go:noescape
func decompress1x_4b_main_loop_bmi2(ctx *decompress1xContext)
func decompress4x_main_loop_asm(ctx *decompress4xContext) {
decompress4x_main_loop_amd64(ctx)
if cpuinfo.HasBMI2() {
decompress4x_main_loop_bmi2(ctx)
} else {
decompress4x_main_loop_amd64(ctx)
}
}
func decompress4x_8b_main_loop_asm(ctx *decompress4xContext) {
decompress4x_8b_main_loop_amd64(ctx)
if cpuinfo.HasBMI2() {
decompress4x_8b_main_loop_bmi2(ctx)
} else {
decompress4x_8b_main_loop_amd64(ctx)
}
}
func decompress4x_4b_main_loop_asm(ctx *decompress4xContext) {
if cpuinfo.HasBMI2() {
decompress4x_4b_main_loop_bmi2(ctx)
} else {
decompress4x_4b_main_loop_amd64(ctx)
}
}
func decompress1x_main_loop_asm(ctx *decompress1xContext) {
@@ -47,3 +110,19 @@ func decompress1x_main_loop_asm(ctx *decompress1xContext) {
decompress1x_main_loop_amd64(ctx)
}
}
func decompress1x_8b_main_loop_asm(ctx *decompress1xContext) {
if cpuinfo.HasBMI2() {
decompress1x_8b_main_loop_bmi2(ctx)
} else {
decompress1x_8b_main_loop_amd64(ctx)
}
}
func decompress1x_4b_main_loop_asm(ctx *decompress1xContext) {
if cpuinfo.HasBMI2() {
decompress1x_4b_main_loop_bmi2(ctx)
} else {
decompress1x_4b_main_loop_amd64(ctx)
}
}
File diff suppressed because it is too large. Load diff
+39 -4
View File
@@ -6,24 +6,47 @@
package huff0
// decompress4x_main_loop_arm64 is an arm64 assembler implementation
// of Decompress4X when tablelog > 8.
// of Decompress4X when tablelog > 8, decoding fastSymbols symbols per
// stream between bit container reloads.
//
//go:noescape
func decompress4x_main_loop_arm64(ctx *decompress4xContext)
// decompress4x_8b_main_loop_arm64 is an arm64 assembler implementation
// of Decompress4X when tablelog <= 8 which decodes 4 entries
// per loop.
// of Decompress4X when tablelog <= 8, decoding fast8bSymbols symbols
// per stream between bit container reloads.
//
//go:noescape
func decompress4x_8b_main_loop_arm64(ctx *decompress4xContext)
// decompress4x_4b_main_loop_arm64 is an arm64 assembler implementation
// of Decompress4X when tablelog <= 4, decoding fast4bSymbols symbols
// per stream between bit container reloads.
//
//go:noescape
func decompress4x_4b_main_loop_arm64(ctx *decompress4xContext)
// decompress1x_main_loop_arm64 is an arm64 assembler implementation
// of Decompress1X when tablelog > 8.
// of Decompress1X when tablelog > 8, decoding fastSymbols symbols
// between bit container reloads.
//
//go:noescape
func decompress1x_main_loop_arm64(ctx *decompress1xContext)
// decompress1x_8b_main_loop_arm64 is an arm64 assembler implementation
// of Decompress1X when tablelog <= 8, decoding fast8bSymbols symbols
// between bit container reloads.
//
//go:noescape
func decompress1x_8b_main_loop_arm64(ctx *decompress1xContext)
// decompress1x_4b_main_loop_arm64 is an arm64 assembler implementation
// of Decompress1X when tablelog <= 4, decoding fast4bSymbols symbols
// between bit container reloads.
//
//go:noescape
func decompress1x_4b_main_loop_arm64(ctx *decompress1xContext)
func decompress4x_main_loop_asm(ctx *decompress4xContext) {
decompress4x_main_loop_arm64(ctx)
}
@@ -32,6 +55,18 @@ func decompress4x_8b_main_loop_asm(ctx *decompress4xContext) {
decompress4x_8b_main_loop_arm64(ctx)
}
func decompress4x_4b_main_loop_asm(ctx *decompress4xContext) {
decompress4x_4b_main_loop_arm64(ctx)
}
func decompress1x_main_loop_asm(ctx *decompress1xContext) {
decompress1x_main_loop_arm64(ctx)
}
func decompress1x_8b_main_loop_asm(ctx *decompress1xContext) {
decompress1x_8b_main_loop_arm64(ctx)
}
func decompress1x_4b_main_loop_asm(ctx *decompress1xContext) {
decompress1x_4b_main_loop_arm64(ctx)
}
File diff suppressed because it is too large. Load diff
+79 -19
View File
@@ -14,6 +14,10 @@ import (
// fallback8BitSize is the size where using Go version is faster.
const fallback8BitSize = 800
// decompress4xContext is the argument block of the Decompress4X asm loops.
// Go fills every field but decoded and inner; the asm advances ip, and on
// return leaves each bit reader in the form bitReaderShifted.restoreFromAsm
// expects.
type decompress4xContext struct {
pbr *[4]bitReaderShifted
peekBits uint8
@@ -21,9 +25,20 @@ type decompress4xContext struct {
dstEvery int
tbl *dEntrySingle
decoded int
limit *byte
limit *byte // stream 0's output pointer must stay below this
ilowest *byte // start of the input block; no read goes below it
ip [4]*byte // each stream's 8-byte input window, advanced by the asm
inner *byte // scratch for the asm: the current inner-loop limit
}
// Symbols decoded per stream between reloads by the 4X asm loops; must
// match the constants of the same names in _generate/gen.go.
const (
fastSymbols = 5 // tablelog 9..11
fast8bSymbols = 7 // tablelog 5..8
fast4bSymbols = 14 // tablelog <= 4
)
// Decompress4X will decompress a 4X encoded stream.
// The length of the supplied input must match the end of a block exactly.
// The *capacity* of the dst slice must match the destination size of
@@ -72,23 +87,55 @@ func (d *Decoder) Decompress4X(dst, src []byte) ([]byte, error) {
var decoded int
if len(out) > 4*4 && !(br[0].off < 4 || br[1].off < 4 || br[2].off < 4 || br[3].off < 4) {
nSyms := fastSymbols
if d.actualTableLog <= 4 {
nSyms = fast4bSymbols
} else if use8BitTables {
nSyms = fast8bSymbols
}
// The asm writes nSyms bytes per stream per iteration and only re-checks
// its bounds between batches of iterations (the batch size is derived
// from limit in _generate/gen.go), so stream 0 must stop early enough
// that stream 3 (which may be up to 3 bytes shorter than dstEvery)
// never writes past the end of out. Every stream needs a full 8-byte
// window ahead of its read pointer to enter the loop.
if limit := dstEvery - nSyms - 2; limit > 0 && br[0].canUseAsm() && br[1].canUseAsm() && br[2].canUseAsm() && br[3].canUseAsm() {
for i := range br {
br[i].prepareForAsm()
}
ctx := decompress4xContext{
pbr: &br,
peekBits: uint8((64 - d.actualTableLog) & 63), // see: bitReaderShifted.peekBitsFast()
out: &out[0],
dstEvery: dstEvery,
tbl: &single[0],
limit: &out[dstEvery-4], // Always stop decoding when first buffer gets here to avoid writing OOB on last.
limit: &out[limit],
// The 6-byte jump table sits below stream 0 inside src, so the
// lowest window may reach into it; restoreFromAsm accounts for
// bytes below a stream's start. Bounding by src rather than by
// stream 0 keeps the asm running about six bytes longer, which
// matters for streams with very short codes.
ilowest: &src[0],
}
if use8BitTables {
for i := range br {
ctx.ip[i] = &br[i].in[br[i].off]
}
switch nSyms {
case fast4bSymbols:
decompress4x_4b_main_loop_asm(&ctx)
case fast8bSymbols:
decompress4x_8b_main_loop_asm(&ctx)
} else {
default:
decompress4x_main_loop_asm(&ctx)
}
decoded = ctx.decoded
out = out[decoded/4:]
for i := range br {
if err := br[i].restoreFromAsm(); err != nil {
return nil, err
}
}
}
// Decode remaining.
@@ -128,18 +175,20 @@ func (d *Decoder) Decompress4X(dst, src []byte) ([]byte, error) {
return dst, nil
}
// decompress1xContext is the argument block of the Decompress1X asm loops.
// Go fills every field but decoded; the asm advances ip, and on return
// leaves the bit reader in the form bitReaderShifted.restoreFromAsm expects.
type decompress1xContext struct {
pbr *bitReaderShifted
peekBits uint8
out *byte
outCap int
outCap int // no write reaches out[outCap]
tbl *dEntrySingle
decoded int
ilowest *byte // start of the stream; no read goes below it
ip *byte // the 8-byte input window, advanced by the asm
}
// Error reported by asm implementations
const error_max_decoded_size_exeeded = -1
// Decompress1X will decompress a 1X encoded stream.
// The cap of the output buffer will be the maximum decompressed size.
// The length of the supplied input must match the end of a block exactly.
@@ -158,25 +207,36 @@ func (d *Decoder) Decompress1X(dst, src []byte) ([]byte, error) {
const tlSize = 1 << tableLogMax
const tlMask = tlSize - 1
if maxDecodedSize >= 4 {
// The asm decodes whole batches of symbols and only re-checks its
// bounds between them, so it needs at least one batch of room (the
// output bound rounds nSyms up to 16, see _generate/gen.go) and a full
// 8-byte window ahead of the read pointer to enter the loop. It also
// needs at most 7 bits consumed on entry so that a batch cannot run
// the container dry, which prepareForAsm establishes.
decoded := 0
if maxDecodedSize >= 16 && br.canUseAsm() {
br.prepareForAsm()
ctx := decompress1xContext{
pbr: &br,
out: &dst[0],
outCap: maxDecodedSize,
peekBits: uint8((64 - d.actualTableLog) & 63), // see: bitReaderShifted.peekBitsFast()
tbl: &d.dt.single[0],
ilowest: &src[0],
ip: &br.in[br.off],
}
decompress1x_main_loop_asm(&ctx)
if ctx.decoded == error_max_decoded_size_exeeded {
return nil, ErrMaxDecodedSizeExceeded
if d.actualTableLog <= 4 {
decompress1x_4b_main_loop_asm(&ctx)
} else if d.actualTableLog <= 8 {
decompress1x_8b_main_loop_asm(&ctx)
} else {
decompress1x_main_loop_asm(&ctx)
}
dst = dst[:ctx.decoded]
decoded = ctx.decoded
}
dst = dst[:decoded]
// br < 8, so uint8 is fine
bitsLeft := uint8(br.off)*8 + 64 - br.bitsRead
bitsLeft := br.remaining()
for bitsLeft > 0 {
br.fill()
if len(dst) >= maxDecodedSize {
@@ -186,7 +246,7 @@ func (d *Decoder) Decompress1X(dst, src []byte) ([]byte, error) {
v := d.dt.single[br.peekBitsFast(d.actualTableLog)&tlMask]
nBits := uint8(v.entry)
br.advance(nBits)
bitsLeft -= nBits
bitsLeft -= uint(nBits)
dst = append(dst, uint8(v.entry>>8))
}
return dst, br.close()
+3
View File
@@ -240,9 +240,11 @@ func (b *blockDec) decodeBuf(hist *history) error {
b.dst[i] = v
}
hist.appendKeep(b.dst)
hist.decoders.consumeSyncLen(len(b.dst))
return nil
case blockTypeRaw:
hist.appendKeep(b.data)
hist.decoders.consumeSyncLen(len(b.data))
return nil
case blockTypeCompressed:
saved := b.dst
@@ -487,6 +489,7 @@ func (b *blockDec) decodeCompressed(hist *history) error {
}
if hist.decoders.nSeqs == 0 {
b.dst = append(b.dst, hist.decoders.literals...)
hist.decoders.consumeSyncLen(len(hist.decoders.literals))
return nil
}
before := len(hist.decoders.out)
+81 -44
View File
@@ -7,8 +7,11 @@ package zstd
import (
"context"
"encoding/binary"
"errors"
"io"
"maps"
"sync"
"sync/atomic"
"github.com/klauspost/compress/zstd/internal/xxhash"
)
@@ -20,7 +23,8 @@ import (
// A decoder can safely be re-used even if the previous stream failed.
// To release the resources, you must call the Close() function on a decoder.
type Decoder struct {
o decoderOptions
// publishedOptions contains the current immutable decoder options.
publishedOptions atomic.Pointer[decoderOptions]
// Unreferenced decoders, ready for use.
decoders chan *blockDec
@@ -84,9 +88,10 @@ var (
func NewReader(r io.Reader, opts ...DOption) (*Decoder, error) {
initPredefined()
var d Decoder
d.o.setDefault()
for _, o := range opts {
err := o(&d.o)
var o decoderOptions
o.setDefault()
for _, opt := range opts {
err := opt(&o)
if err != nil {
return nil, err
}
@@ -99,17 +104,19 @@ func NewReader(r io.Reader, opts ...DOption) (*Decoder, error) {
}
// Initialize dict map if needed.
if d.o.dicts == nil {
d.o.dicts = make(map[uint32]*dict)
if o.dicts == nil {
o.dicts = make(map[uint32]*dict)
}
// Create decoders
d.decoders = make(chan *blockDec, d.o.concurrent)
for i := 0; i < d.o.concurrent; i++ {
dec := newBlockDec(d.o.lowMem)
dec.localFrame = newFrameDec(d.o)
d.decoders = make(chan *blockDec, o.concurrent)
for i := 0; i < o.concurrent; i++ {
dec := newBlockDec(o.lowMem)
dec.localFrame = newFrameDec(o)
dec.localFrame.o.dicts = nil
d.decoders <- dec
}
d.publishedOptions.Store(&o)
if r == nil {
return &d, nil
@@ -171,6 +178,9 @@ func (d *Decoder) Reset(r io.Reader) error {
d.drainOutput()
d.syncStream.br.r = nil
if d.frame != nil {
d.frame.o.dicts = nil
}
if r == nil {
d.current.err = ErrDecoderNilInput
if len(d.current.b) > 0 {
@@ -180,8 +190,10 @@ func (d *Decoder) Reset(r io.Reader) error {
return nil
}
o := *d.publishedOptions.Load()
// If bytes buffer and < 5MB, do sync decoding anyway.
if bb, ok := r.(byter); ok && bb.Len() < d.o.decodeBufsBelow && !d.o.limitToCap {
if bb, ok := r.(byter); ok && bb.Len() < o.decodeBufsBelow && !o.limitToCap {
bb2 := bb
if debugDecoder {
println("*bytes.Buffer detected, doing sync decode, len:", bb.Len())
@@ -193,18 +205,30 @@ func (d *Decoder) Reset(r io.Reader) error {
}
dst, err := d.DecodeAll(b, dst)
if err == nil {
err = io.EOF
if !errors.Is(err, ErrDecoderSizeExceeded) {
if err == nil {
err = io.EOF
}
// Save output buffer
d.syncStream.dstBuf = dst
d.current.b = dst
d.current.err = err
d.current.flushed = true
if debugDecoder {
println("sync decode to", len(dst), "bytes, err:", err)
}
return nil
}
// Save output buffer
d.syncStream.dstBuf = dst
d.current.b = dst
d.current.err = err
d.current.flushed = true
// The output does not fit WithDecoderMaxMemory, which caps the total
// decoded size when decoding in memory. A Reader follows the
// streaming contract, where the option caps the window instead, so
// decode this input as a stream after all; the shortcut has not
// consumed the reader. Drop the oversized buffer rather than keep it
// for reuse.
if debugDecoder {
println("sync decode to", len(dst), "bytes, err:", err)
println("sync decode exceeded max memory after", len(dst), "bytes; streaming instead")
}
return nil
d.syncStream.dstBuf = nil
}
// Remove current block.
d.stashDecoder()
@@ -217,14 +241,16 @@ func (d *Decoder) Reset(r io.Reader) error {
// Ensure no-one else is still running...
d.streamWg.Wait()
if d.frame == nil {
d.frame = newFrameDec(d.o)
d.frame = newFrameDec(o)
} else {
d.frame.setOptions(o)
}
if d.o.concurrent == 1 {
if o.concurrent == 1 {
return d.startSyncDecoder(r)
}
d.current.output = make(chan decodeOutput, d.o.concurrent)
d.current.output = make(chan decodeOutput, o.concurrent)
ctx, cancel := context.WithCancel(context.Background())
d.current.cancel = cancel
d.streamWg.Add(1)
@@ -238,13 +264,21 @@ func (d *Decoder) Reset(r io.Reader) error {
// Options are applied on top of the existing options.
// Some options cannot be changed on reset and will return an error.
func (d *Decoder) ResetWithOptions(r io.Reader, opts ...DOption) error {
d.o.resetOpt = true
defer func() { d.o.resetOpt = false }()
if d.current.err == ErrDecoderClosed {
return d.current.err
}
next := *d.publishedOptions.Load()
next.dicts = maps.Clone(next.dicts)
next.resetOpt = true
for _, o := range opts {
if err := o(&d.o); err != nil {
if err := o(&next); err != nil {
next.resetOpt = false
d.publishedOptions.Store(&next)
return err
}
}
next.resetOpt = false
d.publishedOptions.Store(&next)
return d.Reset(r)
}
@@ -324,6 +358,8 @@ func (d *Decoder) DecodeAll(input, dst []byte) ([]byte, error) {
// Grab a block decoder and frame decoder.
block := <-d.decoders
frame := block.localFrame
o := *d.publishedOptions.Load()
frame.setOptions(o)
initialSize := len(dst)
defer func() {
if debugDecoder {
@@ -335,6 +371,7 @@ func (d *Decoder) DecodeAll(input, dst []byte) ([]byte, error) {
frame.history.decoders.br.in = nil
frame.history.decoders.br.cursor = 0
}
frame.o.dicts = nil
d.decoders <- block
}()
frame.bBuf = input
@@ -354,20 +391,20 @@ func (d *Decoder) DecodeAll(input, dst []byte) ([]byte, error) {
if err = d.setDict(frame); err != nil {
return nil, err
}
if frame.WindowSize > d.o.maxWindowSize {
if frame.WindowSize > frame.o.maxWindowSize {
if debugDecoder {
println("window size exceeded:", frame.WindowSize, ">", d.o.maxWindowSize)
println("window size exceeded:", frame.WindowSize, ">", frame.o.maxWindowSize)
}
return dst, ErrWindowSizeExceeded
}
if frame.FrameContentSize != fcsUnknown {
if frame.FrameContentSize > d.o.maxDecodedSize-uint64(len(dst)-initialSize) {
if frame.FrameContentSize > frame.o.maxDecodedSize-uint64(len(dst)-initialSize) {
if debugDecoder {
println("decoder size exceeded; fcs:", frame.FrameContentSize, "> mcs:", d.o.maxDecodedSize-uint64(len(dst)-initialSize), "len:", len(dst))
println("decoder size exceeded; fcs:", frame.FrameContentSize, "> mcs:", frame.o.maxDecodedSize-uint64(len(dst)-initialSize), "len:", len(dst))
}
return dst, ErrDecoderSizeExceeded
}
if d.o.limitToCap && frame.FrameContentSize > uint64(cap(dst)-len(dst)) {
if frame.o.limitToCap && frame.FrameContentSize > uint64(cap(dst)-len(dst)) {
if debugDecoder {
println("decoder size exceeded; fcs:", frame.FrameContentSize, "> (cap-len)", cap(dst)-len(dst))
}
@@ -380,14 +417,14 @@ func (d *Decoder) DecodeAll(input, dst []byte) ([]byte, error) {
}
}
if cap(dst) == 0 && !d.o.limitToCap {
if cap(dst) == 0 && !frame.o.limitToCap {
// Allocate len(input) * 2 by default if nothing is provided
// and we didn't get frame content size.
size := min(
// Cap to 1 MB.
len(input)*2, 1<<20)
if uint64(size) > d.o.maxDecodedSize {
size = int(d.o.maxDecodedSize)
if uint64(size) > frame.o.maxDecodedSize {
size = int(frame.o.maxDecodedSize)
}
dst = make([]byte, 0, size)
}
@@ -396,7 +433,7 @@ func (d *Decoder) DecodeAll(input, dst []byte) ([]byte, error) {
if err != nil {
return dst, err
}
if uint64(len(dst)-initialSize) > d.o.maxDecodedSize {
if uint64(len(dst)-initialSize) > frame.o.maxDecodedSize {
return dst, ErrDecoderSizeExceeded
}
if len(frame.bBuf) == 0 {
@@ -459,7 +496,7 @@ func (d *Decoder) nextBlock(blocking bool) (ok bool) {
println("got", len(d.current.b), "bytes, error:", d.current.err, "data crc:", tmp)
}
if d.o.ignoreChecksum {
if d.frame.o.ignoreChecksum {
return true
}
@@ -497,7 +534,7 @@ func (d *Decoder) nextBlockSync() (ok bool) {
if d.current.err != nil {
return false
}
if d.frame.WindowSize > d.o.maxDecodedSize || d.frame.WindowSize > d.o.maxWindowSize {
if d.frame.WindowSize > d.frame.o.maxDecodedSize || d.frame.WindowSize > d.frame.o.maxWindowSize {
d.current.err = ErrDecoderSizeExceeded
return false
}
@@ -546,11 +583,11 @@ func (d *Decoder) nextBlockSync() (ok bool) {
// Update/Check CRC
if d.frame.HasCheckSum {
if !d.o.ignoreChecksum {
if !d.frame.o.ignoreChecksum {
d.frame.crc.Write(d.current.b)
}
if d.current.d.Last {
if !d.o.ignoreChecksum {
if !d.frame.o.ignoreChecksum {
d.current.err = d.frame.checkCRC()
} else {
d.current.err = d.frame.consumeCRC()
@@ -656,8 +693,8 @@ func (d *Decoder) startStreamDecoder(ctx context.Context, r io.Reader, output ch
defer d.streamWg.Done()
br := readerWrapper{r: r}
var seqDecode = make(chan *blockDec, d.o.concurrent)
var seqExecute = make(chan *blockDec, d.o.concurrent)
var seqDecode = make(chan *blockDec, d.frame.o.concurrent)
var seqExecute = make(chan *blockDec, d.frame.o.concurrent)
// Async 1: Decode sequences...
go func() {
@@ -858,9 +895,9 @@ decodeStream:
if err == nil {
err = d.setDict(frame)
}
if err == nil && d.frame.WindowSize > d.o.maxWindowSize {
if err == nil && d.frame.WindowSize > d.frame.o.maxWindowSize {
if debugDecoder {
println("decoder size exceeded, fws:", d.frame.WindowSize, "> mws:", d.o.maxWindowSize)
println("decoder size exceeded, fws:", d.frame.WindowSize, "> mws:", d.frame.o.maxWindowSize)
}
err = ErrDecoderSizeExceeded
@@ -940,7 +977,7 @@ decodeStream:
}
func (d *Decoder) setDict(frame *frameDec) (err error) {
dict, ok := d.o.dicts[frame.DictionaryID]
dict, ok := frame.o.dicts[frame.DictionaryID]
if ok {
if debugDecoder {
println("setting dict", frame.DictionaryID)
+7 -4
View File
@@ -49,13 +49,16 @@ const (
)
func newFrameDec(o decoderOptions) *frameDec {
d := frameDec{}
d.setOptions(o)
return &d
}
func (d *frameDec) setOptions(o decoderOptions) {
if o.maxWindowSize > o.maxDecodedSize {
o.maxWindowSize = o.maxDecodedSize
}
d := frameDec{
o: o,
}
return &d
d.o = o
}
// reset will read the frame header and prepare for block decoding.
+4 -6
View File
@@ -47,12 +47,10 @@ init_main_loop_condition:
// Spread symbols
// Calculate table step
MOVD R0, R8
LSR $0x01, R8, R8
MOVD R0, R9
LSR $0x03, R9, R9
ADD R9, R8, R8
ADD $3, R8, R8
LSR $0x01, R0, R8
LSR $0x03, R0, R9
ADD R9, R8, R8
ADD $3, R8, R8
// Fill add bits values
SUB $1, R0, R9
+23
View File
@@ -99,6 +99,29 @@ func (s *sequenceDecs) initialize(br *bitReader, hist *history, out []byte) erro
return nil
}
// consumeSyncLen accounts for n bytes of frame output produced outside
// decodeSync. maxSyncLen bounds how much the frame may still produce and
// decodeSyncSimple compares it with the output buffer's slack to decide
// whether the extended 16-byte copies are safe. Blocks with sequences
// subtracted their output; raw blocks, RLE blocks and compressed blocks
// holding only literals did not, so a frame that opened with a raw block
// overstated its remaining size for every block after it and ran the
// bounds-exact copies for the rest of the frame.
//
// Zero means "no bound" and selects the conservative paths, so a block that
// meets or exceeds the bound lands there; the frame-size checks in
// runDecoder reject the excess afterwards.
func (s *sequenceDecs) consumeSyncLen(n int) {
if s.maxSyncLen == 0 {
return
}
if uint64(n) >= s.maxSyncLen {
s.maxSyncLen = 0
return
}
s.maxSyncLen -= uint64(n)
}
func (s *sequenceDecs) freeDecoders() {
if f := s.litLengths.fse; f != nil && !f.preDefined {
fseDecoderPool.Put(f)
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+4 -3
View File
@@ -83,9 +83,10 @@ const errorOverread = 6
// When the output and literal buffers have compressedBlockOverAlloc (16)
// bytes of slack past their logical use, the assembly may use extended
// memory copies that read and write in 16-byte blocks, overrunning the end
// of a literal run or match by up to 15 bytes. Otherwise it must use the
// bounds-exact ("safe") copies. This mirrors the analogous, always-dynamic
// selection in executeSimple below.
// of a literal run or match by up to 15 bytes; an empty literal run still
// copies one block, reading and writing the full 16 bytes of slack past
// its position. Otherwise it must use the bounds-exact ("safe") copies.
// This mirrors the analogous, always-dynamic selection in executeSimple below.
//
// The unsafe copies were disabled in #644 (2022) as a mitigation for a
// crash, but that crash's root cause — an unguarded bitReader overread that
+54 -2
View File
@@ -470,8 +470,60 @@
"args": [
{
"index": 0,
"value": 40,
"op": "SCMP_CMP_GT"
"value": 41,
"op": "SCMP_CMP_EQ"
}
]
},
{
"names": [
"socket"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 42,
"op": "SCMP_CMP_EQ"
}
]
},
{
"names": [
"socket"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 43,
"op": "SCMP_CMP_EQ"
}
]
},
{
"names": [
"socket"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 44,
"op": "SCMP_CMP_EQ"
}
]
},
{
"names": [
"socket"
],
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 0,
"value": 45,
"op": "SCMP_CMP_EQ"
}
]
},
+134 -54
View File
@@ -1,20 +1,137 @@
// Copyright The Moby Authors.
// SPDX-FileCopyrightText: Copyright The Moby Authors
// SPDX-License-Identifier: Apache-2.0
package seccomp
import (
"slices"
"github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
)
// The socket rules in DefaultProfile rely on AF_ALG and AF_VSOCK being
// exactly two apart (38 and 40), with a single family (39) between them.
var (
_ [38]byte = [unix.AF_ALG]byte{}
_ [40]byte = [unix.AF_VSOCK]byte{}
_ [1]byte = [unix.AF_VSOCK - unix.AF_ALG - 1]byte{}
)
// These are the Linux socket domains currently defined by the UAPI:
// https://github.com/torvalds/linux/blob/master/include/linux/socket.h
// AF_UNSPEC is not a creatable domain, AF_MAX is a sentinel, and AF_LOCAL/AF_FILE
// and AF_ROUTE are aliases of AF_UNIX and AF_NETLINK respectively.
var allowedSocketDomains = []uint64{
unix.AF_UNIX,
unix.AF_INET,
unix.AF_AX25,
unix.AF_IPX,
unix.AF_APPLETALK,
unix.AF_NETROM,
unix.AF_BRIDGE,
unix.AF_ATMPVC,
unix.AF_X25,
unix.AF_INET6,
unix.AF_ROSE,
unix.AF_DECnet,
unix.AF_NETBEUI,
unix.AF_SECURITY,
unix.AF_KEY,
unix.AF_NETLINK,
unix.AF_PACKET,
unix.AF_ASH,
unix.AF_ECONET,
unix.AF_ATMSVC,
unix.AF_RDS,
unix.AF_SNA,
unix.AF_IRDA,
unix.AF_PPPOX,
unix.AF_WANPIPE,
unix.AF_LLC,
unix.AF_IB,
unix.AF_MPLS,
unix.AF_CAN,
unix.AF_TIPC,
unix.AF_BLUETOOTH,
unix.AF_IUCV,
unix.AF_RXRPC,
unix.AF_ISDN,
unix.AF_PHONET,
unix.AF_IEEE802154,
unix.AF_CAIF,
// AF_ALG gives userspace direct access to the kernel cryptography API. The
// vulnerabilities demonstrated by https://copy.fail/ have been fixed, but
// general-purpose containers have no practical need for this interface.
// Keep it blocked to avoid exposing an unnecessary kernel attack surface.
//
// unix.AF_ALG,
unix.AF_NFC,
// AF_VSOCK provides host/guest communication. Before Linux 7.0 it was global
// across network namespaces, allowing a container to reach any visible VM by
// CID. Linux 7.0 added opt-in namespace isolation for vhost-vsock and
// loopback, but global mode remains the default. Keep it blocked for older
// kernels and default-global configurations; intentional users can provide a
// custom seccomp profile. See https://docs.kernel.org/admin-guide/sysctl/net.html.
//
// unix.AF_VSOCK,
unix.AF_KCM,
unix.AF_QIPCRTR,
unix.AF_SMC,
unix.AF_XDP,
unix.AF_MCTP,
}
func socketSyscalls() []*Syscall {
// Keep range detection independent of the declaration order above.
s := slices.Clone(allowedSocketDomains)
slices.Sort(s)
return socketSyscallsForDomains(s)
}
func socketSyscallsForDomains(domains []uint64) []*Syscall {
syscalls := make([]*Syscall, 0, len(domains))
// runc treats repeated comparisons for one argument as separate OR rules,
// so bounded ranges cannot use both a lower and an upper comparison.
// See https://github.com/opencontainers/runc/issues/2735.
//
// A one-sided range starting at AF_UNIX is safe because AF_UNSPEC is not a
// creatable domain. Collapse it only when doing so removes equality rules.
rangeEnd := 0
if len(domains) > 1 && domains[0] == unix.AF_UNIX && domains[1] == domains[0]+1 {
// The first domains are represented by the range instead of equalities.
rangeEnd = 2
lastDomain := domains[1]
for _, domain := range domains[2:] {
// Stop at the first gap so the range does not include a blocked domain.
if domain != lastDomain+1 {
break
}
lastDomain = domain
rangeEnd++
}
// OpLessThan is exclusive, so compare with the value after the range.
syscalls = append(syscalls, socketSyscall(lastDomain+1, specs.OpLessThan))
}
// Every later run is bounded, so preserve it with one equality per domain.
for _, domain := range domains[rangeEnd:] {
syscalls = append(syscalls, socketSyscall(domain, specs.OpEqualTo))
}
return syscalls
}
func socketSyscall(value uint64, op specs.LinuxSeccompOperator) *Syscall {
return &Syscall{
LinuxSyscall: specs.LinuxSyscall{
Names: []string{"socket"},
Action: specs.ActAllow,
Args: []specs.LinuxSeccompArg{
{
Index: 0,
Value: value,
Op: op,
},
},
},
}
}
func arches() []Architecture {
return []Architecture{
@@ -442,51 +559,14 @@ func DefaultProfile() *Seccomp {
MinKernel: &KernelVersion{4, 8},
},
},
// Allow socket(2) for all address families except AF_VSOCK and AF_ALG.
// NOTE: on 32-bit x86, socket() goes through socketcall(2) which is
// allowed unconditionally above, so AF_VSOCK/AF_ALG is still reachable
// via the socketcall-based socket() path. These arg filters only apply
// to the direct socket syscall, and do not protect 32-bit x86 unless
// socketcall(2) is also addressed.
{
LinuxSyscall: specs.LinuxSyscall{
Names: []string{"socket"},
Action: specs.ActAllow,
Args: []specs.LinuxSeccompArg{
{
Index: 0,
Value: unix.AF_ALG,
Op: specs.OpLessThan,
},
},
},
},
{
LinuxSyscall: specs.LinuxSyscall{
Names: []string{"socket"},
Action: specs.ActAllow,
Args: []specs.LinuxSeccompArg{
{
Index: 0,
Value: unix.AF_ALG + 1,
Op: specs.OpEqualTo,
},
},
},
},
{
LinuxSyscall: specs.LinuxSyscall{
Names: []string{"socket"},
Action: specs.ActAllow,
Args: []specs.LinuxSeccompArg{
{
Index: 0,
Value: unix.AF_VSOCK,
Op: specs.OpGreaterThan,
},
},
},
},
}
// Allow socket(2) for the address families listed in allowedSocketDomains.
// On ABIs that use socketcall(2), the socket arguments are behind a pointer
// and cannot be filtered by seccomp. Because socketcall(2) is allowed above,
// this domain allow-list applies only to the direct socket syscall.
syscalls = append(syscalls, socketSyscalls()...)
syscalls = append(syscalls, []*Syscall{
{
LinuxSyscall: specs.LinuxSyscall{
Names: []string{"personality"},
@@ -874,7 +954,7 @@ func DefaultProfile() *Seccomp {
Caps: []string{"CAP_PERFMON"},
},
},
}
}...)
errnoRet := uint(unix.EPERM)
return &Seccomp{
+9 -18
View File
@@ -1,4 +1,4 @@
// Copyright The Moby Authors.
// SPDX-FileCopyrightText: Copyright The Moby Authors
// SPDX-License-Identifier: Apache-2.0
package seccomp
@@ -10,24 +10,15 @@ import (
"golang.org/x/sys/unix"
)
var (
currentKernelVersion *KernelVersion
kernelVersionError error
once sync.Once
)
// getKernelVersion gets the current kernel version.
func getKernelVersion() (*KernelVersion, error) {
once.Do(func() {
var uts unix.Utsname
if err := unix.Uname(&uts); err != nil {
return
}
// Remove the \x00 from the release for Atoi to parse correctly
currentKernelVersion, kernelVersionError = parseRelease(unix.ByteSliceToString(uts.Release[:]))
})
return currentKernelVersion, kernelVersionError
}
var getKernelVersion = sync.OnceValues(func() (*KernelVersion, error) {
var uts unix.Utsname
if err := unix.Uname(&uts); err != nil {
return nil, err
}
// Convert the NUL-terminated release field before parsing.
return parseRelease(unix.ByteSliceToString(uts.Release[:]))
})
// parseRelease parses a string and creates a KernelVersion based on it.
func parseRelease(release string) (*KernelVersion, error) {
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright The Moby Authors.
// SPDX-FileCopyrightText: Copyright The Moby Authors
// SPDX-License-Identifier: Apache-2.0
package seccomp
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright The Moby Authors.
// SPDX-FileCopyrightText: Copyright The Moby Authors
// SPDX-License-Identifier: Apache-2.0
//go:generate go run -tags 'seccomp' generate.go
+3 -3
View File
@@ -562,7 +562,7 @@ github.com/dimchansky/utfbom
# github.com/distribution/reference v0.6.0
## explicit; go 1.20
github.com/distribution/reference
# github.com/docker/cli v29.8.0+incompatible
# github.com/docker/cli v29.8.1+incompatible
## explicit
github.com/docker/cli/cli/config
github.com/docker/cli/cli/config/configfile
@@ -780,7 +780,7 @@ github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common
github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.1
github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2
github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1
# github.com/klauspost/compress v1.20.0
# github.com/klauspost/compress v1.20.1
## explicit; go 1.25
github.com/klauspost/compress
github.com/klauspost/compress/flate
@@ -822,7 +822,7 @@ github.com/moby/policy-helpers/image
github.com/moby/policy-helpers/roots
github.com/moby/policy-helpers/roots/dhi
github.com/moby/policy-helpers/types
# github.com/moby/profiles/seccomp v0.2.3
# github.com/moby/profiles/seccomp v0.2.4
## explicit; go 1.23.0
github.com/moby/profiles/seccomp
# github.com/moby/sys/mount v0.3.5