code wiki / (root) / nx_zstd_huf.nx

nx_zstd_huf.nx source

↩ module page · 161 lines · 5607 B

1// nx_zstd_huf.nx -- Zstandard's Huffman decode table (the X1 flat form). 2// 3// The literals section's engine. zstd does not walk a Huffman tree bit by bit; 4// it builds a FLAT table of 2^tableLog entries where each symbol occupies a 5// contiguous run, peeks tableLog bits to index it in one step, then consumes 6// only the code's true length. That is why the reader needs peek and skip as 7// separate operations. 8// 9// SLOT COUNT IS DRIVEN BY WEIGHT, NOT LENGTH. A symbol of weight w occupies 10// 2^(w-1) slots, which is the same as 2^(tableLog - length) since 11// length = tableLog + 1 - w. Both forms appear in the literature and they are 12// equal only when the table log is right -- so a table built with the wrong 13// log fills a plausible number of slots and still fails to tile the table. 14// The build REFUSES unless the slots sum to exactly 2^tableLog. 15// 16// RANKS ARE LAID OUT BY DESCENDING WEIGHT. Heaviest symbols (shortest codes) 17// take the lowest table indices. Laying them out in symbol order instead 18// produces a table that decodes self-consistently and disagrees with every 19// other zstd implementation -- the failure mode is a file that only this 20// decoder can read. 21// 22// genealogy_id: zstandard_rfc8878_huffman 23// lineage_id: nx_zstd_huf_v1 24// license_tier: ORIGINAL 25 26import "nx_syscalls.nx" 27import "nx_zstd_bits.nx" 28 29const NX_HUF_MAX_LOG: i64 = 12 30const NX_HUF_MAX_SYMBOLS: i64 = 256 31const NX_HUF_TBL_BYTES: i64 = 40 32 33struct NxZstdHuf { 34 table_log: i64, 35 table_size: i64, 36 symbol: i64, 37 nbits: i64, 38} 39 40// ===== build the flat table ======================================= 41// 42// weights[0..count-1] are the COMPLETED weight set (including the inferred 43// last one). Returns the table, or 0 if the slots do not tile 2^tableLog 44// exactly -- a partial tile means some peek value indexes an entry that was 45// never written, and that entry would decode as symbol 0 forever. 46 47func nx_zstd_huf_build(weights: *i64, count: i64, table_log: i64) -> *NxZstdHuf { 48 if count <= 0 { return 0 as *NxZstdHuf } 49 if count > NX_HUF_MAX_SYMBOLS { return 0 as *NxZstdHuf } 50 if table_log <= 0 { return 0 as *NxZstdHuf } 51 if table_log > NX_HUF_MAX_LOG { return 0 as *NxZstdHuf } 52 53 let size: i64 = 1 << table_log 54 55 // rank_count[w] = how many symbols carry weight w 56 let rank_count: *i64 = sys_mmap((NX_HUF_MAX_LOG + 4) * 8) as *i64 57 var i: i64 = 0 58 while i <= NX_HUF_MAX_LOG + 1 { rank_count[i] = 0; i = i + 1 } 59 60 var total: i64 = 0 61 i = 0 62 while i < count { 63 let w: i64 = weights[i] 64 if w < 0 { return 0 as *NxZstdHuf } 65 if w > table_log { return 0 as *NxZstdHuf } 66 if w > 0 { 67 rank_count[w] = rank_count[w] + 1 68 total = total + (1 << (w - 1)) 69 } 70 i = i + 1 71 } 72 // the slots must tile the table EXACTLY -- no gaps, no overlap 73 if total != size { return 0 as *NxZstdHuf } 74 75 // rank_start[w] = first table index for weight w, laid out heaviest first 76 let rank_start: *i64 = sys_mmap((NX_HUF_MAX_LOG + 4) * 8) as *i64 77 var next: i64 = 0 78 var w2: i64 = table_log 79 while w2 >= 1 { 80 rank_start[w2] = next 81 next = next + (rank_count[w2] << (w2 - 1)) 82 w2 = w2 - 1 83 } 84 85 let sym: *i64 = sys_mmap(size * 8 + 64) as *i64 86 let nb: *i64 = sys_mmap(size * 8 + 64) as *i64 87 i = 0 88 while i < size { sym[i] = 0 - 1; nb[i] = 0; i = i + 1 } 89 90 // fill each symbol's contiguous run, heaviest weight first so that the 91 // shortest codes occupy the lowest indices 92 w2 = table_log 93 while w2 >= 1 { 94 var s: i64 = 0 95 while s < count { 96 if weights[s] == w2 { 97 let slots: i64 = 1 << (w2 - 1) 98 let len: i64 = table_log + 1 - w2 99 var k: i64 = 0 100 while k < slots { 101 let idx: i64 = rank_start[w2] + k 102 if idx >= size { return 0 as *NxZstdHuf } 103 sym[idx] = s 104 nb[idx] = len 105 k = k + 1 106 } 107 rank_start[w2] = rank_start[w2] + slots 108 } 109 s = s + 1 110 } 111 w2 = w2 - 1 112 } 113 114 // every entry must have been written 115 i = 0 116 while i < size { 117 if sym[i] < 0 { return 0 as *NxZstdHuf } 118 i = i + 1 119 } 120 121 let t: *NxZstdHuf = sys_mmap(NX_HUF_TBL_BYTES) as *NxZstdHuf 122 t.table_log = table_log 123 t.table_size = size 124 t.symbol = sym as i64 125 t.nbits = nb as i64 126 return t 127} 128 129// ===== accessors ================================================== 130 131func nx_zstd_huf_symbol_at(t: *NxZstdHuf, idx: i64) -> i64 { 132 if t == (0 as *NxZstdHuf) { return 0 - 1 } 133 if idx < 0 { return 0 - 1 } 134 if idx >= t.table_size { return 0 - 1 } 135 let a: *i64 = t.symbol as *i64 136 return a[idx] 137} 138 139func nx_zstd_huf_nbits_at(t: *NxZstdHuf, idx: i64) -> i64 { 140 if t == (0 as *NxZstdHuf) { return 0 - 1 } 141 if idx < 0 { return 0 - 1 } 142 if idx >= t.table_size { return 0 - 1 } 143 let a: *i64 = t.nbits as *i64 144 return a[idx] 145} 146 147// ===== decode one symbol ========================================== 148// 149// Peek tableLog bits, index, consume only the code's true length. 150 151func nx_zstd_huf_decode(t: *NxZstdHuf, b: *NxZstdBits) -> i64 { 152 if t == (0 as *NxZstdHuf) { return 0 - 1 } 153 if b == (0 as *NxZstdBits) { return 0 - 1 } 154 let idx: i64 = nx_zstd_bits_peek(b, t.table_log) 155 let s: i64 = nx_zstd_huf_symbol_at(t, idx) 156 if s < 0 { return 0 - 1 } 157 let n: i64 = nx_zstd_huf_nbits_at(t, idx) 158 if n <= 0 { return 0 - 1 } 159 nx_zstd_bits_skip(b, n) 160 return s 161}