block.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. package lz4block
  2. import (
  3. "encoding/binary"
  4. "math/bits"
  5. "sync"
  6. "github.com/pierrec/lz4/v4/internal/lz4errors"
  7. )
  8. const (
  9. // The following constants are used to setup the compression algorithm.
  10. minMatch = 4 // the minimum size of the match sequence size (4 bytes)
  11. winSizeLog = 16 // LZ4 64Kb window size limit
  12. winSize = 1 << winSizeLog
  13. winMask = winSize - 1 // 64Kb window of previous data for dependent blocks
  14. // hashLog determines the size of the hash table used to quickly find a previous match position.
  15. // Its value influences the compression speed and memory usage, the lower the faster,
  16. // but at the expense of the compression ratio.
  17. // 16 seems to be the best compromise for fast compression.
  18. hashLog = 16
  19. htSize = 1 << hashLog
  20. mfLimit = 10 + minMatch // The last match cannot start within the last 14 bytes.
  21. )
  22. func recoverBlock(e *error) {
  23. if r := recover(); r != nil && *e == nil {
  24. *e = lz4errors.ErrInvalidSourceShortBuffer
  25. }
  26. }
  27. // blockHash hashes the lower 6 bytes into a value < htSize.
  28. func blockHash(x uint64) uint32 {
  29. const prime6bytes = 227718039650203
  30. return uint32(((x << (64 - 48)) * prime6bytes) >> (64 - hashLog))
  31. }
  32. func CompressBlockBound(n int) int {
  33. return n + n/255 + 16
  34. }
  35. func UncompressBlock(src, dst, dict []byte) (int, error) {
  36. if len(src) == 0 {
  37. return 0, nil
  38. }
  39. if di := decodeBlock(dst, src, dict); di >= 0 {
  40. return di, nil
  41. }
  42. return 0, lz4errors.ErrInvalidSourceShortBuffer
  43. }
  44. type Compressor struct {
  45. // Offsets are at most 64kiB, so we can store only the lower 16 bits of
  46. // match positions: effectively, an offset from some 64kiB block boundary.
  47. //
  48. // When we retrieve such an offset, we interpret it as relative to the last
  49. // block boundary si &^ 0xffff, or the one before, (si &^ 0xffff) - 0x10000,
  50. // depending on which of these is inside the current window. If a table
  51. // entry was generated more than 64kiB back in the input, we find out by
  52. // inspecting the input stream.
  53. table [htSize]uint16
  54. // Bitmap indicating which positions in the table are in use.
  55. // This allows us to quickly reset the table for reuse,
  56. // without having to zero everything.
  57. inUse [htSize / 32]uint32
  58. }
  59. // Get returns the position of a presumptive match for the hash h.
  60. // The match may be a false positive due to a hash collision or an old entry.
  61. // If si < winSize, the return value may be negative.
  62. func (c *Compressor) get(h uint32, si int) int {
  63. h &= htSize - 1
  64. i := 0
  65. if c.inUse[h/32]&(1<<(h%32)) != 0 {
  66. i = int(c.table[h])
  67. }
  68. i += si &^ winMask
  69. if i >= si {
  70. // Try previous 64kiB block (negative when in first block).
  71. i -= winSize
  72. }
  73. return i
  74. }
  75. func (c *Compressor) put(h uint32, si int) {
  76. h &= htSize - 1
  77. c.table[h] = uint16(si)
  78. c.inUse[h/32] |= 1 << (h % 32)
  79. }
  80. func (c *Compressor) reset() { c.inUse = [htSize / 32]uint32{} }
  81. var compressorPool = sync.Pool{New: func() interface{} { return new(Compressor) }}
  82. func CompressBlock(src, dst []byte) (int, error) {
  83. c := compressorPool.Get().(*Compressor)
  84. n, err := c.CompressBlock(src, dst)
  85. compressorPool.Put(c)
  86. return n, err
  87. }
  88. func (c *Compressor) CompressBlock(src, dst []byte) (int, error) {
  89. // Zero out reused table to avoid non-deterministic output (issue #65).
  90. c.reset()
  91. // Return 0, nil only if the destination buffer size is < CompressBlockBound.
  92. isNotCompressible := len(dst) < CompressBlockBound(len(src))
  93. // adaptSkipLog sets how quickly the compressor begins skipping blocks when data is incompressible.
  94. // This significantly speeds up incompressible data and usually has very small impact on compression.
  95. // bytes to skip = 1 + (bytes since last match >> adaptSkipLog)
  96. const adaptSkipLog = 7
  97. // si: Current position of the search.
  98. // anchor: Position of the current literals.
  99. var si, di, anchor int
  100. sn := len(src) - mfLimit
  101. if sn <= 0 {
  102. goto lastLiterals
  103. }
  104. // Fast scan strategy: the hash table only stores the last 4 bytes sequences.
  105. for si < sn {
  106. // Hash the next 6 bytes (sequence)...
  107. match := binary.LittleEndian.Uint64(src[si:])
  108. h := blockHash(match)
  109. h2 := blockHash(match >> 8)
  110. // We check a match at s, s+1 and s+2 and pick the first one we get.
  111. // Checking 3 only requires us to load the source one.
  112. ref := c.get(h, si)
  113. ref2 := c.get(h2, si+1)
  114. c.put(h, si)
  115. c.put(h2, si+1)
  116. offset := si - ref
  117. if offset <= 0 || offset >= winSize || uint32(match) != binary.LittleEndian.Uint32(src[ref:]) {
  118. // No match. Start calculating another hash.
  119. // The processor can usually do this out-of-order.
  120. h = blockHash(match >> 16)
  121. ref3 := c.get(h, si+2)
  122. // Check the second match at si+1
  123. si += 1
  124. offset = si - ref2
  125. if offset <= 0 || offset >= winSize || uint32(match>>8) != binary.LittleEndian.Uint32(src[ref2:]) {
  126. // No match. Check the third match at si+2
  127. si += 1
  128. offset = si - ref3
  129. c.put(h, si)
  130. if offset <= 0 || offset >= winSize || uint32(match>>16) != binary.LittleEndian.Uint32(src[ref3:]) {
  131. // Skip one extra byte (at si+3) before we check 3 matches again.
  132. si += 2 + (si-anchor)>>adaptSkipLog
  133. continue
  134. }
  135. }
  136. }
  137. // Match found.
  138. lLen := si - anchor // Literal length.
  139. // We already matched 4 bytes.
  140. mLen := 4
  141. // Extend backwards if we can, reducing literals.
  142. tOff := si - offset - 1
  143. for lLen > 0 && tOff >= 0 && src[si-1] == src[tOff] {
  144. si--
  145. tOff--
  146. lLen--
  147. mLen++
  148. }
  149. // Add the match length, so we continue search at the end.
  150. // Use mLen to store the offset base.
  151. si, mLen = si+mLen, si+minMatch
  152. // Find the longest match by looking by batches of 8 bytes.
  153. for si+8 <= sn {
  154. x := binary.LittleEndian.Uint64(src[si:]) ^ binary.LittleEndian.Uint64(src[si-offset:])
  155. if x == 0 {
  156. si += 8
  157. } else {
  158. // Stop is first non-zero byte.
  159. si += bits.TrailingZeros64(x) >> 3
  160. break
  161. }
  162. }
  163. mLen = si - mLen
  164. if di >= len(dst) {
  165. return 0, lz4errors.ErrInvalidSourceShortBuffer
  166. }
  167. if mLen < 0xF {
  168. dst[di] = byte(mLen)
  169. } else {
  170. dst[di] = 0xF
  171. }
  172. // Encode literals length.
  173. if lLen < 0xF {
  174. dst[di] |= byte(lLen << 4)
  175. } else {
  176. dst[di] |= 0xF0
  177. di++
  178. l := lLen - 0xF
  179. for ; l >= 0xFF && di < len(dst); l -= 0xFF {
  180. dst[di] = 0xFF
  181. di++
  182. }
  183. if di >= len(dst) {
  184. return 0, lz4errors.ErrInvalidSourceShortBuffer
  185. }
  186. dst[di] = byte(l)
  187. }
  188. di++
  189. // Literals.
  190. if di+lLen > len(dst) {
  191. return 0, lz4errors.ErrInvalidSourceShortBuffer
  192. }
  193. copy(dst[di:di+lLen], src[anchor:anchor+lLen])
  194. di += lLen + 2
  195. anchor = si
  196. // Encode offset.
  197. if di > len(dst) {
  198. return 0, lz4errors.ErrInvalidSourceShortBuffer
  199. }
  200. dst[di-2], dst[di-1] = byte(offset), byte(offset>>8)
  201. // Encode match length part 2.
  202. if mLen >= 0xF {
  203. for mLen -= 0xF; mLen >= 0xFF && di < len(dst); mLen -= 0xFF {
  204. dst[di] = 0xFF
  205. di++
  206. }
  207. if di >= len(dst) {
  208. return 0, lz4errors.ErrInvalidSourceShortBuffer
  209. }
  210. dst[di] = byte(mLen)
  211. di++
  212. }
  213. // Check if we can load next values.
  214. if si >= sn {
  215. break
  216. }
  217. // Hash match end-2
  218. h = blockHash(binary.LittleEndian.Uint64(src[si-2:]))
  219. c.put(h, si-2)
  220. }
  221. lastLiterals:
  222. if isNotCompressible && anchor == 0 {
  223. // Incompressible.
  224. return 0, nil
  225. }
  226. // Last literals.
  227. if di >= len(dst) {
  228. return 0, lz4errors.ErrInvalidSourceShortBuffer
  229. }
  230. lLen := len(src) - anchor
  231. if lLen < 0xF {
  232. dst[di] = byte(lLen << 4)
  233. } else {
  234. dst[di] = 0xF0
  235. di++
  236. for lLen -= 0xF; lLen >= 0xFF && di < len(dst); lLen -= 0xFF {
  237. dst[di] = 0xFF
  238. di++
  239. }
  240. if di >= len(dst) {
  241. return 0, lz4errors.ErrInvalidSourceShortBuffer
  242. }
  243. dst[di] = byte(lLen)
  244. }
  245. di++
  246. // Write the last literals.
  247. if isNotCompressible && di >= anchor {
  248. // Incompressible.
  249. return 0, nil
  250. }
  251. if di+len(src)-anchor > len(dst) {
  252. return 0, lz4errors.ErrInvalidSourceShortBuffer
  253. }
  254. di += copy(dst[di:di+len(src)-anchor], src[anchor:])
  255. return di, nil
  256. }
  257. // blockHash hashes 4 bytes into a value < winSize.
  258. func blockHashHC(x uint32) uint32 {
  259. const hasher uint32 = 2654435761 // Knuth multiplicative hash.
  260. return x * hasher >> (32 - winSizeLog)
  261. }
  262. type CompressorHC struct {
  263. // hashTable: stores the last position found for a given hash
  264. // chainTable: stores previous positions for a given hash
  265. hashTable, chainTable [htSize]int
  266. needsReset bool
  267. }
  268. var compressorHCPool = sync.Pool{New: func() interface{} { return new(CompressorHC) }}
  269. func CompressBlockHC(src, dst []byte, depth CompressionLevel) (int, error) {
  270. c := compressorHCPool.Get().(*CompressorHC)
  271. n, err := c.CompressBlock(src, dst, depth)
  272. compressorHCPool.Put(c)
  273. return n, err
  274. }
  275. func (c *CompressorHC) CompressBlock(src, dst []byte, depth CompressionLevel) (_ int, err error) {
  276. if c.needsReset {
  277. // Zero out reused table to avoid non-deterministic output (issue #65).
  278. c.hashTable = [htSize]int{}
  279. c.chainTable = [htSize]int{}
  280. }
  281. c.needsReset = true // Only false on first call.
  282. defer recoverBlock(&err)
  283. // Return 0, nil only if the destination buffer size is < CompressBlockBound.
  284. isNotCompressible := len(dst) < CompressBlockBound(len(src))
  285. // adaptSkipLog sets how quickly the compressor begins skipping blocks when data is incompressible.
  286. // This significantly speeds up incompressible data and usually has very small impact on compression.
  287. // bytes to skip = 1 + (bytes since last match >> adaptSkipLog)
  288. const adaptSkipLog = 7
  289. var si, di, anchor int
  290. sn := len(src) - mfLimit
  291. if sn <= 0 {
  292. goto lastLiterals
  293. }
  294. if depth == 0 {
  295. depth = winSize
  296. }
  297. for si < sn {
  298. // Hash the next 4 bytes (sequence).
  299. match := binary.LittleEndian.Uint32(src[si:])
  300. h := blockHashHC(match)
  301. // Follow the chain until out of window and give the longest match.
  302. mLen := 0
  303. offset := 0
  304. for next, try := c.hashTable[h], depth; try > 0 && next > 0 && si-next < winSize; next, try = c.chainTable[next&winMask], try-1 {
  305. // The first (mLen==0) or next byte (mLen>=minMatch) at current match length
  306. // must match to improve on the match length.
  307. if src[next+mLen] != src[si+mLen] {
  308. continue
  309. }
  310. ml := 0
  311. // Compare the current position with a previous with the same hash.
  312. for ml < sn-si {
  313. x := binary.LittleEndian.Uint64(src[next+ml:]) ^ binary.LittleEndian.Uint64(src[si+ml:])
  314. if x == 0 {
  315. ml += 8
  316. } else {
  317. // Stop is first non-zero byte.
  318. ml += bits.TrailingZeros64(x) >> 3
  319. break
  320. }
  321. }
  322. if ml < minMatch || ml <= mLen {
  323. // Match too small (<minMath) or smaller than the current match.
  324. continue
  325. }
  326. // Found a longer match, keep its position and length.
  327. mLen = ml
  328. offset = si - next
  329. // Try another previous position with the same hash.
  330. }
  331. c.chainTable[si&winMask] = c.hashTable[h]
  332. c.hashTable[h] = si
  333. // No match found.
  334. if mLen == 0 {
  335. si += 1 + (si-anchor)>>adaptSkipLog
  336. continue
  337. }
  338. // Match found.
  339. // Update hash/chain tables with overlapping bytes:
  340. // si already hashed, add everything from si+1 up to the match length.
  341. winStart := si + 1
  342. if ws := si + mLen - winSize; ws > winStart {
  343. winStart = ws
  344. }
  345. for si, ml := winStart, si+mLen; si < ml; {
  346. match >>= 8
  347. match |= uint32(src[si+3]) << 24
  348. h := blockHashHC(match)
  349. c.chainTable[si&winMask] = c.hashTable[h]
  350. c.hashTable[h] = si
  351. si++
  352. }
  353. lLen := si - anchor
  354. si += mLen
  355. mLen -= minMatch // Match length does not include minMatch.
  356. if mLen < 0xF {
  357. dst[di] = byte(mLen)
  358. } else {
  359. dst[di] = 0xF
  360. }
  361. // Encode literals length.
  362. if lLen < 0xF {
  363. dst[di] |= byte(lLen << 4)
  364. } else {
  365. dst[di] |= 0xF0
  366. di++
  367. l := lLen - 0xF
  368. for ; l >= 0xFF; l -= 0xFF {
  369. dst[di] = 0xFF
  370. di++
  371. }
  372. dst[di] = byte(l)
  373. }
  374. di++
  375. // Literals.
  376. copy(dst[di:di+lLen], src[anchor:anchor+lLen])
  377. di += lLen
  378. anchor = si
  379. // Encode offset.
  380. di += 2
  381. dst[di-2], dst[di-1] = byte(offset), byte(offset>>8)
  382. // Encode match length part 2.
  383. if mLen >= 0xF {
  384. for mLen -= 0xF; mLen >= 0xFF; mLen -= 0xFF {
  385. dst[di] = 0xFF
  386. di++
  387. }
  388. dst[di] = byte(mLen)
  389. di++
  390. }
  391. }
  392. if isNotCompressible && anchor == 0 {
  393. // Incompressible.
  394. return 0, nil
  395. }
  396. // Last literals.
  397. lastLiterals:
  398. lLen := len(src) - anchor
  399. if lLen < 0xF {
  400. dst[di] = byte(lLen << 4)
  401. } else {
  402. dst[di] = 0xF0
  403. di++
  404. lLen -= 0xF
  405. for ; lLen >= 0xFF; lLen -= 0xFF {
  406. dst[di] = 0xFF
  407. di++
  408. }
  409. dst[di] = byte(lLen)
  410. }
  411. di++
  412. // Write the last literals.
  413. if isNotCompressible && di >= anchor {
  414. // Incompressible.
  415. return 0, nil
  416. }
  417. di += copy(dst[di:di+len(src)-anchor], src[anchor:])
  418. return di, nil
  419. }