Seq Stream Format - YadeWira/lz6 GitHub Wiki
Seq Stream Format
This is the byte layout (seq v2, since v1.6.4-pre; offset codes and table header revised in v1.6.6-pre) of one block produced by LZ6_compress_seq, as read by LZ6_decompress_seq (lib/entropy/lz6seq.c). All multi-byte integers are little-endian unless marked otherwise. The format is a lab format: it changes between -pre versions (the last break was the x86 filter block in v1.6.9-pre).
Notation:
vlqis a 255-continuation integer: add bytes while the byte equals 255.- An rANS stream is
[table][slen:4][x:4, big-endian][slen-4 bytes], wherexis the initial decoder state. - A table is
[L_bits:1][maxSym:1][3 bytes per symbol 0..maxSym: normalised frequency]. Frequencies sum toM = 2^L_bits.
Block header
[flags:1][isize:4]
| flags | meaning | payload |
|---|---|---|
| bit 4 set | x86-filtered block (since v1.6.9-pre) | see x86 filter blocks below |
| bit 3 set | byte-plane block | see Plane blocks below |
| bit 2 set | PRNG regeneration | [seed:2]. Output byte i = glibc_random() >> (8 * (flags & 3)) |
| bit 0 set | stored | isize raw bytes |
| otherwise | normal seq block | the sections below, in order |
1. Literals
[lit_mode:1][lit_count:vlq] ...
| lit_mode | name | layout after lit_count |
|---|---|---|
| 0 | raw | lit_count bytes |
| 1 | FSE order-0 | [csize:vlq] + rANS stream |
| 5 | Huffman | [csize:vlq][k:1][256 code lengths][total_bits:4][MSB-first bitstream]. Canonical codes, lengths ≤ 16, lookup table of k ≤ 12 bits |
| 7 | Huffman, 4 streams (since v1.6.8-pre) | [csize:vlq][k:1][256 code lengths] then 4 × [total_bits:4][MSB-first bitstream], one code table. Stream j holds literals j·s .. min((j+1)·s, lit_count), with s = ceil(lit_count/4). The encoder uses it from 4096 literals up |
| 6 | FSE order-1, 16 contexts | [csize:vlq][L_bits:1][16 × (L_bits:1 + 256×freq:2)][slen:4][x:4 BE][stream]. Context = previous literal >> 4 |
| 3 | FSE order-1, 256 contexts | [csize:vlq][active bitmap:32][L_bits:1][fallback table][table per active context][slen:4][x:4 BE][stream]. Context = previous literal; inactive contexts use the fallback table |
2. Sequences (v2, since v1.6.4-pre)
[sc:4]
[ll table][ml table][of table]
[bits len:4][8 zero pad bytes][backward bitstream] (present when sc > 0)
Table (one per symbol stream, table ANS as in zstd's FSE): [log:1][maxSym:1] then the normalised counts of symbols 0..maxSym. log is 5..12 and the counts sum to 2^log. Each count is a varint: a byte v < 128 is the count; otherwise the count is (v & 127) | next_byte << 7. A count of 0 is followed by one byte r: r more symbols also have count 0.
- Spread: position
poswalks(pos + (size/2 + size/8 + 3)) & (size-1), and symbolsfillsnorm[s]positions in order. - Decode entry
u:x = next[s]++(nextstarts atnorm[s]),nbBits = log - highbit(x),newState = (x << nbBits) - size.
Alphabets:
- ll (≤ 35): literal-length code. Value =
LL_base[c]+LL_extra[c]extra bits. Codes 0..15 are one value each (no extra bits); codecin 16..34 covers[LL_base[c], LL_base[c+1])withlog2(step)extra bits, i.e.1,1,1,1,2,2,2,2,3,3,3,4,4,4,4,5,5,5,5for codes 16..34; code 35 is the open-ended escape with 24 bits (v1.6.11-pre; before it the widths of codes 20..34 were wider than the step, so most of those raw bits were always zero). - ml (≤ 36):
0= no match (the last sequence carries only literals). Otherwise codec-1givesML_base[c-1]+ML_extra[c-1]extra bits. Minimum match is 3. The ml extra widths are the same as ll's (1,1,1,1,2,2,2,2,3,3,3,4,4,4,4,5,5,5,5for codes 16..34, 24 for the escape). - of (≤ 193):
0..190= new offset,191..193= rep0 / rep1 / rep2.- Codes
0..30: offsets 1..31 exactly (codec= offsetc+1), no extra bits. - Codes
31..190: bucketb = 5..24(b = floor(log2 offset)) and the offset's low 3 bitsj: code =31 + 8*(b-5) + j. Thenb-3raw bitsmfollow, and offset =2^b + j + (m << 3). - Keeping the low 3 bits in the entropy-coded symbol captures the alignment of structured data (record strides, 4- and 8-byte fields), which raw bits cannot.
- Codes
The rep stack starts at {0,0,0}:
- A rep1/rep2 hit moves that entry to the front.
- A new offset (code ≤ 190) is always pushed onto the stack. The encoder codes an offset equal to rep0 as rep0, so a new-offset code equal to rep0 never occurs in its streams; a decoder still pushes it like any other (the reference decoder before v1.6.11 skipped the push in that case, which cost 2-4% of its instructions for a case no encoder produces;
bakeoff/gate/rpzero.cpins the rule).
A block is valid only if its sequences produce exactly the declared size (the literals-only last sequence closes it; a block whose sequences stop short is rejected, where v1.6.10-pre returned the shorter length) and the bitstream is consumed exactly.
Bitstream. It is written forward, LSB-first, and read backward from its last byte. The highest set bit of the last byte is an end marker. The 8 zero bytes in front keep every 8-byte load in bounds; a valid stream consumes exactly its bits and none of the padding.
The reader first takes the three initial states: ll (log_ll bits), of (log_of), ml (log_ml). Then, for each sequence n:
- Its symbols are the table entries at the current ll/ml/of states.
- If ml ≠ 0 and of ≤ 190: read the offset's raw bits (
b-3for codes ≥ 31, none below). - If ml ≠ 0: read the ml extra bits.
- Read the ll extra bits.
- If
nis not the last sequence: update the states in the order ll, ml, of, each asnewState + read(nbBits).
The encoder writes all of this in exact reverse: it runs from the last sequence to the first, encoding the of, ml and ll symbols, then writing the ll, ml and of extras.
Sequence semantics
For each sequence in order:
- Copy
llliterals. - If
ml == 0, stop. - Otherwise copy
mlbytes fromoffsetbytes back. The copy may overlap, and the offset must be ≤ bytes produced so far.
There is no external dictionary: each block is self-contained.
x86 filter blocks
[flags: 0x10][isize:4][nr:vlq][nr x (start:4, len:4)][inner block]
- The inner block is a complete seq block of the filtered data (normal, plane or stored; never another x86 block) whose
isizeequals the outer one. - Each
(start, len)is a byte range of the output (sorted, non-overlapping,len >= 5, insideisize). - To decode: decode the inner block, then apply the inverse E8/E9 filter in place to each range, with
now_pos = start. The filter is the classic x86 branch converter of xz / 7-Zip (CALL/JMP rel32 <-> absolute, with itsprev_maskstate machine); it is a bijection on any byte string, and its state resets at the start of every range. - The encoder picks ranges from 64 KB chunks with a high density of E8/E9 opcodes followed by a 00/FF byte, and keeps one only if a level-1 trial of its first 512 KB is at least 0.5% smaller when filtered.
Plane blocks
[flags: 0x08 | s][isize:4] then per lane: [mode:1][...]
- Stride is 4 if
(flags & 3) == 2, else 2.isizeis a multiple of the stride. - Lane
jholds bytesj, j+stride, j+2·stride, ...
| lane mode | payload |
|---|---|
| 0 | raw: isize/stride bytes |
| 1 | [csize:4] + a normal seq block (header included) |
| 2 | [csize:4] + a Huffman stream (as literal mode 5, without lit_count) |