master
1const std = @import("std");
2const builtin = @import("builtin");
3
4pub const min_length = 3;
5pub const max_length = 258;
6
7pub const min_distance = 1;
8pub const max_distance = std.compress.flate.history_len;
9
10pub const codegen_order: [19]u8 = .{
11 16, 17, 18,
12 0, 8, //
13 7, 9,
14 6, 10,
15 5, 11,
16 4, 12,
17 3, 13,
18 2, 14,
19 1, 15,
20};
21
22pub const fixed_lit_codes = fixed_lit[0];
23pub const fixed_lit_bits = fixed_lit[1];
24const fixed_lit = blk: {
25 var codes: [286]u16 = undefined;
26 var bits: [286]u4 = undefined;
27
28 for (0..143 + 1, 0b00110000..0b10111111 + 1) |i, v| {
29 codes[i] = @bitReverse(@as(u8, v));
30 bits[i] = 8;
31 }
32 for (144..255 + 1, 0b110010000..0b111111111 + 1) |i, v| {
33 codes[i] = @bitReverse(@as(u9, v));
34 bits[i] = 9;
35 }
36 for (256..279 + 1, 0b0000000..0b0010111 + 1) |i, v| {
37 codes[i] = @bitReverse(@as(u7, v));
38 bits[i] = 7;
39 }
40 for (280..287 - 2 + 1, 0b11000000..0b11000111 - 2 + 1) |i, v| {
41 codes[i] = @bitReverse(@as(u8, v));
42 bits[i] = 8;
43 }
44 break :blk .{ codes, bits };
45};
46
47pub const fixed_dist_codes = fixed_dist[0];
48pub const fixed_dist_bits = fixed_dist[1];
49const fixed_dist = blk: {
50 var codes: [30]u16 = undefined;
51 const bits: [30]u4 = @splat(5);
52
53 for (0..30) |i| {
54 codes[i] = @bitReverse(@as(u5, i));
55 }
56 break :blk .{ codes, bits };
57};
58
59// All paramters of codes can be derived matchematically, however some are faster to
60// do via lookup table. For ReleaseSmall, we do all mathematically to save space.
61pub const LenCode = if (builtin.mode != .ReleaseSmall) LookupLenCode else ShortLenCode;
62pub const DistCode = if (builtin.mode != .ReleaseSmall) LookupDistCode else ShortDistCode;
63const ShortLenCode = ShortCode(u8, u2, u3, true);
64const ShortDistCode = ShortCode(u15, u1, u4, false);
65/// For length and distance codes, they having this format.
66///
67/// For example, length code 0b1101 (13 or literal 270) has high_bits=0b01 and high_log2=3
68/// and is 1_01_xx (2 extra bits). It is then offsetted by the min length of 3.
69/// ^ bit 4 = 2 + high_log2 - 1
70///
71/// An exception is Length codes, where value 255 is assigned the special zero-bit code 28 or
72/// literal 285.
73fn ShortCode(Value: type, HighBits: type, HighLog2: type, len_special: bool) type {
74 return packed struct(u5) {
75 /// Bits preceding high bit or start if none
76 high_bits: HighBits,
77 /// High bit, 0 means none, otherwise it is at bit `x + high_log2 - 1`
78 high_log2: HighLog2,
79
80 pub fn fromVal(v: Value) @This() {
81 if (len_special and v == 255) return .fromInt(28);
82 const high_bits = @bitSizeOf(HighBits) + 1;
83 const bits = @bitSizeOf(Value) - @clz(v);
84 if (bits <= high_bits) return @bitCast(@as(u5, @intCast(v)));
85 const high = v >> @intCast(bits - high_bits);
86 return .{ .high_bits = @truncate(high), .high_log2 = @intCast(bits - high_bits + 1) };
87 }
88
89 /// `@ctz(return) >= extraBits()`
90 pub fn base(c: @This()) Value {
91 if (len_special and c.toInt() == 28) return 255;
92 if (c.high_log2 <= 1) return @as(u5, @bitCast(c));
93 const high_value = (@as(Value, @intFromBool(c.high_log2 != 0)) << @bitSizeOf(HighBits)) | c.high_bits;
94 const high_start = @as(std.math.Log2Int(Value), c.high_log2 - 1);
95 return @shlExact(high_value, high_start);
96 }
97
98 const max_extra = @bitSizeOf(Value) - (1 + @bitSizeOf(HighLog2));
99 pub fn extraBits(c: @This()) std.math.IntFittingRange(0, max_extra) {
100 if (len_special and c.toInt() == 28) return 0;
101 return @intCast(c.high_log2 -| 1);
102 }
103
104 pub fn toInt(c: @This()) u5 {
105 return @bitCast(c);
106 }
107
108 pub fn fromInt(x: u5) @This() {
109 return @bitCast(x);
110 }
111 };
112}
113
114const LookupLenCode = packed struct(u5) {
115 code: ShortLenCode,
116
117 const code_table = table: {
118 var codes: [256]ShortLenCode = undefined;
119 for (0.., &codes) |v, *c| {
120 c.* = .fromVal(v);
121 }
122 break :table codes;
123 };
124
125 const base_table = table: {
126 var bases: [29]u8 = undefined;
127 for (0.., &bases) |c, *b| {
128 b.* = ShortLenCode.fromInt(c).base();
129 }
130 break :table bases;
131 };
132
133 pub fn fromVal(v: u8) LookupLenCode {
134 return .{ .code = code_table[v] };
135 }
136
137 /// `@ctz(return) >= extraBits()`
138 pub fn base(c: LookupLenCode) u8 {
139 return base_table[c.toInt()];
140 }
141
142 pub fn extraBits(c: LookupLenCode) u3 {
143 return c.code.extraBits();
144 }
145
146 pub fn toInt(c: LookupLenCode) u5 {
147 return @bitCast(c);
148 }
149
150 pub fn fromInt(x: u5) LookupLenCode {
151 return @bitCast(x);
152 }
153};
154
155const LookupDistCode = packed struct(u5) {
156 code: ShortDistCode,
157
158 const base_table = table: {
159 var bases: [30]u15 = undefined;
160 for (0.., &bases) |c, *b| {
161 b.* = ShortDistCode.fromInt(c).base();
162 }
163 break :table bases;
164 };
165
166 pub fn fromVal(v: u15) LookupDistCode {
167 return .{ .code = .fromVal(v) };
168 }
169
170 /// `@ctz(return) >= extraBits()`
171 pub fn base(c: LookupDistCode) u15 {
172 return base_table[c.toInt()];
173 }
174
175 pub fn extraBits(c: LookupDistCode) u4 {
176 return c.code.extraBits();
177 }
178
179 pub fn toInt(c: LookupDistCode) u5 {
180 return @bitCast(c);
181 }
182
183 pub fn fromInt(x: u5) LookupDistCode {
184 return @bitCast(x);
185 }
186};
187
188test LenCode {
189 inline for ([_]type{ ShortLenCode, LookupLenCode }) |Code| {
190 // Check against the RFC 1951 table
191 for (0.., [_]struct {
192 base: u8,
193 extra_bits: u4,
194 }{
195 // zig fmt: off
196 .{ .base = 3 - min_length, .extra_bits = 0 },
197 .{ .base = 4 - min_length, .extra_bits = 0 },
198 .{ .base = 5 - min_length, .extra_bits = 0 },
199 .{ .base = 6 - min_length, .extra_bits = 0 },
200 .{ .base = 7 - min_length, .extra_bits = 0 },
201 .{ .base = 8 - min_length, .extra_bits = 0 },
202 .{ .base = 9 - min_length, .extra_bits = 0 },
203 .{ .base = 10 - min_length, .extra_bits = 0 },
204 .{ .base = 11 - min_length, .extra_bits = 1 },
205 .{ .base = 13 - min_length, .extra_bits = 1 },
206 .{ .base = 15 - min_length, .extra_bits = 1 },
207 .{ .base = 17 - min_length, .extra_bits = 1 },
208 .{ .base = 19 - min_length, .extra_bits = 2 },
209 .{ .base = 23 - min_length, .extra_bits = 2 },
210 .{ .base = 27 - min_length, .extra_bits = 2 },
211 .{ .base = 31 - min_length, .extra_bits = 2 },
212 .{ .base = 35 - min_length, .extra_bits = 3 },
213 .{ .base = 43 - min_length, .extra_bits = 3 },
214 .{ .base = 51 - min_length, .extra_bits = 3 },
215 .{ .base = 59 - min_length, .extra_bits = 3 },
216 .{ .base = 67 - min_length, .extra_bits = 4 },
217 .{ .base = 83 - min_length, .extra_bits = 4 },
218 .{ .base = 99 - min_length, .extra_bits = 4 },
219 .{ .base = 115 - min_length, .extra_bits = 4 },
220 .{ .base = 131 - min_length, .extra_bits = 5 },
221 .{ .base = 163 - min_length, .extra_bits = 5 },
222 .{ .base = 195 - min_length, .extra_bits = 5 },
223 .{ .base = 227 - min_length, .extra_bits = 5 },
224 .{ .base = 258 - min_length, .extra_bits = 0 },
225 }) |code, params| {
226 // zig fmt: on
227 const c: u5 = @intCast(code);
228 try std.testing.expectEqual(params.extra_bits, Code.extraBits(.fromInt(@intCast(c))));
229 try std.testing.expectEqual(params.base, Code.base(.fromInt(@intCast(c))));
230 for (params.base..params.base + @shlExact(@as(u16, 1), params.extra_bits) -
231 @intFromBool(c == 27)) |v|
232 {
233 try std.testing.expectEqual(c, Code.fromVal(@intCast(v)).toInt());
234 }
235 }
236 }
237}
238
239test DistCode {
240 inline for ([_]type{ ShortDistCode, LookupDistCode }) |Code| {
241 for (0.., [_]struct {
242 base: u15,
243 extra_bits: u4,
244 }{
245 // zig fmt: off
246 .{ .base = 1 - min_distance, .extra_bits = 0 },
247 .{ .base = 2 - min_distance, .extra_bits = 0 },
248 .{ .base = 3 - min_distance, .extra_bits = 0 },
249 .{ .base = 4 - min_distance, .extra_bits = 0 },
250 .{ .base = 5 - min_distance, .extra_bits = 1 },
251 .{ .base = 7 - min_distance, .extra_bits = 1 },
252 .{ .base = 9 - min_distance, .extra_bits = 2 },
253 .{ .base = 13 - min_distance, .extra_bits = 2 },
254 .{ .base = 17 - min_distance, .extra_bits = 3 },
255 .{ .base = 25 - min_distance, .extra_bits = 3 },
256 .{ .base = 33 - min_distance, .extra_bits = 4 },
257 .{ .base = 49 - min_distance, .extra_bits = 4 },
258 .{ .base = 65 - min_distance, .extra_bits = 5 },
259 .{ .base = 97 - min_distance, .extra_bits = 5 },
260 .{ .base = 129 - min_distance, .extra_bits = 6 },
261 .{ .base = 193 - min_distance, .extra_bits = 6 },
262 .{ .base = 257 - min_distance, .extra_bits = 7 },
263 .{ .base = 385 - min_distance, .extra_bits = 7 },
264 .{ .base = 513 - min_distance, .extra_bits = 8 },
265 .{ .base = 769 - min_distance, .extra_bits = 8 },
266 .{ .base = 1025 - min_distance, .extra_bits = 9 },
267 .{ .base = 1537 - min_distance, .extra_bits = 9 },
268 .{ .base = 2049 - min_distance, .extra_bits = 10 },
269 .{ .base = 3073 - min_distance, .extra_bits = 10 },
270 .{ .base = 4097 - min_distance, .extra_bits = 11 },
271 .{ .base = 6145 - min_distance, .extra_bits = 11 },
272 .{ .base = 8193 - min_distance, .extra_bits = 12 },
273 .{ .base = 12289 - min_distance, .extra_bits = 12 },
274 .{ .base = 16385 - min_distance, .extra_bits = 13 },
275 .{ .base = 24577 - min_distance, .extra_bits = 13 },
276 }) |code, params| {
277 // zig fmt: on
278 const c: u5 = @intCast(code);
279 try std.testing.expectEqual(params.extra_bits, Code.extraBits(.fromInt(@intCast(c))));
280 try std.testing.expectEqual(params.base, Code.base(.fromInt(@intCast(c))));
281 for (params.base..params.base + @shlExact(@as(u16, 1), params.extra_bits)) |v| {
282 try std.testing.expectEqual(c, Code.fromVal(@intCast(v)).toInt());
283 }
284 }
285 }
286}