nx_huffman.nx source
↩ module page · 304 lines · 10049 B
1// nx_huffman.nx -- canonical Huffman decoder.
2//
3// Composes against nx_bitstream. Used by:
4// - DEFLATE (RFC 1951) -- literal/length + distance trees
5// - JPEG -- DHT segments (DC + AC coefficient tables)
6// - MP3 / AAC / Vorbis / Opus -- spectral coefficient codes
7// - brotli -- many sub-tables
8// - FLAC -- Rice partitions (not strictly Huffman; close family)
9//
10// Canonical Huffman is the convention where:
11// - codes are assigned ONLY by their length sequence
12// - shortest codes first; within a length, in symbol order
13// - given just an array of (length per symbol), the decoder
14// can reconstruct the tree
15//
16// Build algorithm (RFC 1951 section 3.2.2):
17// 1. count[L] = number of symbols with length L
18// 2. first_code[L] = (first_code[L-1] + count[L-1]) << 1
19// 3. sort symbols by (length, symbol index) -> symbol_table
20// 4. first_symbol[L] = cumulative count of lengths < L
21//
22// Decode algorithm:
23// - read bits MSB-first into accumulator
24// - for L = 1..max_length:
25// if accumulator < first_code[L] + count[L]:
26// idx = accumulator - first_code[L]
27// return symbol_table[first_symbol[L] + idx]
28// else:
29// shift accumulator left by 1, read next bit, OR in
30// - return -1 (invalid bitstream)
31//
32// genealogy_id: rfc1951_canonical_huffman_1996
33// lineage_id: nx_huffman_v1
34
35// nx_safety_envelope:
36// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
37// sil_target: SIL1
38// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
39// verdict: NOT_YET_EVALUATED
40
41import "nx_syscalls.nx"
42import "nx_runtime.nx"
43import "nx_tier.nx"
44import "nx_bitstream.nx"
45
46// ===== bounds =====================================================
47
48const NX_HUFF_MAX_SYMBOLS: nx_int = 65536 // covers DEFLATE 288 + JPEG 256 + most use cases
49const NX_HUFF_MAX_LENGTH: nx_int = 32
50
51// ===== table struct ==============================================
52
53struct NxHuffmanTable {
54 n_symbols: nx_int,
55 max_length: nx_int,
56 count: *nx_int, // count[L] for L in [0, max_length]
57 first_code: *nx_int, // first_code[L]
58 first_symbol: *nx_int, // first_symbol[L] (offset into symbol_table)
59 symbol_table: *nx_int, // n_symbols entries sorted by (length, symbol)
60}
61
62const NX_HUFF_TABLE_BYTES: nx_size = 56
63
64// ===== build ======================================================
65//
66// code_lengths[s] = length of code for symbol s (0 = absent from
67// table). Returns NULL if input invalid.
68
69func nx_huffman_build(code_lengths: *nx_int, n_symbols: nx_int) -> *NxHuffmanTable {
70 if n_symbols <= 0 { return 0 as *NxHuffmanTable }
71 if n_symbols > NX_HUFF_MAX_SYMBOLS { return 0 as *NxHuffmanTable }
72
73 let p: *u8 = sys_mmap(NX_HUFF_TABLE_BYTES)
74 let t: *NxHuffmanTable = p as *NxHuffmanTable
75 t.n_symbols = n_symbols
76
77 // Step 1: count[L] for L in [0, NX_HUFF_MAX_LENGTH].
78 let count_bytes: nx_size = ((NX_HUFF_MAX_LENGTH + 1) as nx_size) * 8
79 let count: *nx_int = (sys_mmap(count_bytes)) as *nx_int
80 var i: nx_int = 0
81 while i <= NX_HUFF_MAX_LENGTH {
82 count[i] = 0
83 i = i + 1
84 }
85 var s: nx_int = 0
86 var max_len: nx_int = 0
87 while s < n_symbols {
88 let L: nx_int = code_lengths[s]
89 if L > 0 {
90 if L > NX_HUFF_MAX_LENGTH { return 0 as *NxHuffmanTable }
91 count[L] = count[L] + 1
92 if L > max_len { max_len = L }
93 }
94 s = s + 1
95 }
96 t.count = count
97 t.max_length = max_len
98
99 // Step 2: first_code[L].
100 let first_code_bytes: nx_size = ((NX_HUFF_MAX_LENGTH + 1) as nx_size) * 8
101 let first_code: *nx_int = (sys_mmap(first_code_bytes)) as *nx_int
102 first_code[0] = 0
103 var fc: nx_int = 0
104 var L: nx_int = 1
105 while L <= NX_HUFF_MAX_LENGTH {
106 fc = (fc + count[L - 1]) * 2
107 first_code[L] = fc
108 L = L + 1
109 }
110 t.first_code = first_code
111
112 // Step 3: first_symbol[L] = cumulative count of (length < L).
113 let first_symbol_bytes: nx_size = ((NX_HUFF_MAX_LENGTH + 1) as nx_size) * 8
114 let first_symbol: *nx_int = (sys_mmap(first_symbol_bytes)) as *nx_int
115 first_symbol[0] = 0
116 var fs: nx_int = 0
117 var FL: nx_int = 1
118 while FL <= NX_HUFF_MAX_LENGTH {
119 first_symbol[FL] = fs
120 fs = fs + count[FL]
121 FL = FL + 1
122 }
123 t.first_symbol = first_symbol
124
125 // Step 4: symbol_table sorted by (length, symbol index).
126 let symtab_bytes: nx_size = (n_symbols as nx_size) * 8
127 let symtab: *nx_int = (sys_mmap(symtab_bytes)) as *nx_int
128
129 // running write-offset per length
130 let next_off_bytes: nx_size = ((NX_HUFF_MAX_LENGTH + 1) as nx_size) * 8
131 let next_off: *nx_int = (sys_mmap(next_off_bytes)) as *nx_int
132 var ni: nx_int = 0
133 while ni <= NX_HUFF_MAX_LENGTH {
134 next_off[ni] = first_symbol[ni]
135 ni = ni + 1
136 }
137 var sym: nx_int = 0
138 while sym < n_symbols {
139 let len: nx_int = code_lengths[sym]
140 if len > 0 {
141 let dst_idx: nx_int = next_off[len]
142 symtab[dst_idx] = sym
143 next_off[len] = dst_idx + 1
144 }
145 sym = sym + 1
146 }
147 t.symbol_table = symtab
148 return t
149}
150
151// ===== decode one symbol (MSB-first bit reading) ==================
152//
153// Reads bits one at a time, accumulating into `code`, until `code`
154// fits within an in-range canonical code. Returns the matching
155// symbol, or -1 on bitstream error / invalid code.
156
157func nx_huffman_decode_msb(t: *NxHuffmanTable, bs: *NxBitStream) -> nx_int {
158 var code: nx_int = 0
159 var L: nx_int = 1
160 while L <= t.max_length {
161 let bit: nx_int = nx_bitstream_read_msb(bs, 1)
162 if bs.overflow == 1 { return -1 }
163 code = (code << 1) | bit
164 let fc: nx_int = t.first_code[L]
165 let cnt: nx_int = t.count[L]
166 if code < (fc + cnt) {
167 if code >= fc {
168 let idx: nx_int = code - fc
169 let st_off: nx_int = t.first_symbol[L]
170 return t.symbol_table[st_off + idx]
171 }
172 }
173 L = L + 1
174 }
175 return -1
176}
177
178// ===== decode (LSB-first variant; DEFLATE convention) ============
179//
180// DEFLATE reads Huffman codes LSB-first BUT the bits within each
181// code are also bit-reversed. The substrate's nx_deflate wrapper
182// pre-reverses; this primitive provides the bit-reversed read
183// helper.
184
185func _huff_reverse_bits(v: nx_int, n: nx_int) -> nx_int {
186 var src: nx_int = v
187 var dst: nx_int = 0
188 var i: nx_int = 0
189 while i < n {
190 let bit: nx_int = src & 1
191 dst = (dst << 1) | bit
192 src = src >> 1
193 i = i + 1
194 }
195 return dst
196}
197
198func nx_huffman_decode_lsb(t: *NxHuffmanTable, bs: *NxBitStream) -> nx_int {
199 var code: nx_int = 0
200 var L: nx_int = 1
201 while L <= t.max_length {
202 let bit: nx_int = nx_bitstream_read_lsb(bs, 1)
203 if bs.overflow == 1 { return -1 }
204 code = (code << 1) | bit
205 let fc: nx_int = t.first_code[L]
206 let cnt: nx_int = t.count[L]
207 if code < (fc + cnt) {
208 if code >= fc {
209 let idx: nx_int = code - fc
210 let st_off: nx_int = t.first_symbol[L]
211 return t.symbol_table[st_off + idx]
212 }
213 }
214 L = L + 1
215 }
216 return -1
217}
218
219// ===== self-test ==================================================
220
221func main() -> nx_int {
222 // Toy table: 4 symbols with code-lengths [2, 1, 3, 3].
223 // sym 0: length 2 -> code 10 (binary)
224 // sym 1: length 1 -> code 0
225 // sym 2: length 3 -> code 110
226 // sym 3: length 3 -> code 111
227 let lens: *nx_int = (sys_mmap(32)) as *nx_int
228 lens[0] = 2
229 lens[1] = 1
230 lens[2] = 3
231 lens[3] = 3
232
233 let t: *NxHuffmanTable = nx_huffman_build(lens, 4)
234 if t == (0 as *NxHuffmanTable) { return 1 }
235 if t.n_symbols != 4 { return 2 }
236 if t.max_length != 3 { return 3 }
237
238 // Verify count + first_code.
239 if t.count[1] != 1 { return 4 }
240 if t.count[2] != 1 { return 5 }
241 if t.count[3] != 2 { return 6 }
242 if t.first_code[1] != 0 { return 7 }
243 if t.first_code[2] != 2 { return 8 } // (0 + 1) * 2
244 if t.first_code[3] != 6 { return 9 } // (2 + 1) * 2
245
246 // Verify symbol_table sorted by (length, symbol):
247 // pos 0: symbol 1 (length 1)
248 // pos 1: symbol 0 (length 2)
249 // pos 2: symbol 2 (length 3)
250 // pos 3: symbol 3 (length 3)
251 if t.symbol_table[0] != 1 { return 10 }
252 if t.symbol_table[1] != 0 { return 11 }
253 if t.symbol_table[2] != 2 { return 12 }
254 if t.symbol_table[3] != 3 { return 13 }
255
256 // ---- decode MSB-first ----
257 //
258 // Encode the stream: sym1 sym0 sym2 sym3 sym1
259 // = 0 10 110 111 0 = 0 1011 0111 0 = "0 10 110 111 0"
260 // Concatenated MSB-first: 0101 1011 1101 0...
261 //
262 // Pack into bytes MSB-first:
263 // bit 0 (MSB of byte 0): 0
264 // bit 1: 1
265 // bit 2: 0
266 // bit 3: 1
267 // bit 4: 1
268 // bit 5: 0
269 // bit 6: 1
270 // bit 7: 1
271 // byte 0 = 0101_1011 = 0x5B
272 //
273 // bit 8: 1
274 // bit 9: 1
275 // bit 10: 1
276 // bit 11: 0
277 // bit 12: ... (4 padding bits)
278 // byte 1 = 1110_0000 = 0xE0
279 let buf: *u8 = (sys_mmap(2)) as *u8
280 buf[0] = 0x5B as u8
281 buf[1] = 0xE0 as u8
282
283 let bs: *NxBitStream = nx_bitstream_alloc(buf, 2)
284 let s1: nx_int = nx_huffman_decode_msb(t, bs)
285 if s1 != 1 { return 20 }
286 let s2: nx_int = nx_huffman_decode_msb(t, bs)
287 if s2 != 0 { return 21 }
288 let s3: nx_int = nx_huffman_decode_msb(t, bs)
289 if s3 != 2 { return 22 }
290 let s4: nx_int = nx_huffman_decode_msb(t, bs)
291 if s4 != 3 { return 23 }
292 let s5: nx_int = nx_huffman_decode_msb(t, bs)
293 if s5 != 1 { return 24 }
294
295 // ---- bit-reverse helper ----
296 // reverse(0b101, 3) = 0b101 = 5
297 if _huff_reverse_bits(5, 3) != 5 { return 30 }
298 // reverse(0b110, 3) = 0b011 = 3
299 if _huff_reverse_bits(6, 3) != 3 { return 31 }
300 // reverse(0b1010, 4) = 0b0101 = 5
301 if _huff_reverse_bits(10, 4) != 5 { return 32 }
302
303 return 0
304}