| ... | ... | @@ -0,0 +1,425 @@ |
| 1 | const std = @import("std"); |
| 2 | |
| 3 | pub const AbbrevOp = union(enum) { |
| 4 | literal: u32, // 0 |
| 5 | fixed: u16, // 1 |
| 6 | fixed_runtime: type, // 1 |
| 7 | vbr: u16, // 2 |
| 8 | char6: void, // 4 |
| 9 | blob: void, // 5 |
| 10 | array_fixed: u16, // 3, 1 |
| 11 | array_fixed_runtime: type, // 3, 1 |
| 12 | array_vbr: u16, // 3, 2 |
| 13 | array_char6: void, // 3, 4 |
| 14 | }; |
| 15 | |
| 16 | pub const Error = error{OutOfMemory}; |
| 17 | |
| 18 | pub fn BitcodeWriter(comptime types: []const type) type { |
| 19 | return struct { |
| 20 | const BcWriter = @This(); |
| 21 | |
| 22 | buffer: std.ArrayList(u32), |
| 23 | bit_buffer: u32 = 0, |
| 24 | bit_count: u5 = 0, |
| 25 | |
| 26 | widths: []const u16, |
| 27 | |
| 28 | pub fn getTypeIndex(comptime ty: type) usize { |
| 29 | inline for (types, 0..) |t, i| { |
| 30 | if (t == ty) return i; |
| 31 | } |
| 32 | unreachable; |
| 33 | } |
| 34 | |
| 35 | pub fn init(allocator: std.mem.Allocator, widths: []const u16) BcWriter { |
| 36 | std.debug.assert(widths.len == types.len); |
| 37 | return .{ |
| 38 | .buffer = std.ArrayList(u32).init(allocator), |
| 39 | .widths = widths, |
| 40 | }; |
| 41 | } |
| 42 | |
| 43 | pub fn deinit(self: BcWriter) void { |
| 44 | self.buffer.deinit(); |
| 45 | } |
| 46 | |
| 47 | pub fn toSlice(self: BcWriter) []const u32 { |
| 48 | std.debug.assert(self.bit_count == 0); |
| 49 | return self.buffer.items; |
| 50 | } |
| 51 | |
| 52 | pub fn length(self: BcWriter) usize { |
| 53 | std.debug.assert(self.bit_count == 0); |
| 54 | return self.buffer.items.len; |
| 55 | } |
| 56 | |
| 57 | pub fn writeBits(self: *BcWriter, value: anytype, bits: u16) Error!void { |
| 58 | if (bits == 0) return; |
| 59 | |
| 60 | var in_buffer = bufValue(value, 32); |
| 61 | var in_bits = bits; |
| 62 | |
| 63 | // Store input bits in buffer if they fit otherwise store as many as possible and flush |
| 64 | if (self.bit_count > 0) { |
| 65 | const bits_remaining = 31 - self.bit_count + 1; |
| 66 | const n: u5 = @intCast(@min(bits_remaining, in_bits)); |
| 67 | const v = @as(u32, @truncate(in_buffer)) << self.bit_count; |
| 68 | self.bit_buffer |= v; |
| 69 | in_buffer >>= n; |
| 70 | |
| 71 | self.bit_count +%= n; |
| 72 | in_bits -= n; |
| 73 | |
| 74 | if (self.bit_count != 0) return; |
| 75 | try self.buffer.append(self.bit_buffer); |
| 76 | self.bit_buffer = 0; |
| 77 | } |
| 78 | |
| 79 | // Write 32-bit chunks of input bits |
| 80 | while (in_bits >= 32) { |
| 81 | try self.buffer.append(@truncate(in_buffer)); |
| 82 | |
| 83 | in_buffer >>= 31; |
| 84 | in_buffer >>= 1; |
| 85 | in_bits -= 32; |
| 86 | } |
| 87 | |
| 88 | // Store remaining input bits in buffer |
| 89 | if (in_bits > 0) { |
| 90 | self.bit_count = @intCast(in_bits); |
| 91 | self.bit_buffer = @truncate(in_buffer); |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | pub fn writeVBR(self: *BcWriter, value: anytype, comptime vbr_bits: usize) Error!void { |
| 96 | comptime { |
| 97 | std.debug.assert(vbr_bits > 1); |
| 98 | if (@bitSizeOf(@TypeOf(value)) > 64) @compileError("Unsupported VBR block type: " ++ @typeName(@TypeOf(value))); |
| 99 | } |
| 100 | |
| 101 | var in_buffer = bufValue(value, vbr_bits); |
| 102 | |
| 103 | const continue_bit = @as(@TypeOf(in_buffer), 1) << @intCast(vbr_bits - 1); |
| 104 | const mask = continue_bit - 1; |
| 105 | |
| 106 | // If input is larger than one VBR block can store |
| 107 | // then store vbr_bits - 1 bits and a continue bit |
| 108 | while (in_buffer > mask) { |
| 109 | try self.writeBits(in_buffer & mask | continue_bit, vbr_bits); |
| 110 | in_buffer >>= @intCast(vbr_bits - 1); |
| 111 | } |
| 112 | |
| 113 | // Store remaining bits |
| 114 | try self.writeBits(in_buffer, vbr_bits); |
| 115 | } |
| 116 | |
| 117 | pub fn bitsVBR(_: *const BcWriter, value: anytype, comptime vbr_bits: usize) u16 { |
| 118 | comptime { |
| 119 | std.debug.assert(vbr_bits > 1); |
| 120 | if (@bitSizeOf(@TypeOf(value)) > 64) @compileError("Unsupported VBR block type: " ++ @typeName(@TypeOf(value))); |
| 121 | } |
| 122 | |
| 123 | var bits: u16 = 0; |
| 124 | |
| 125 | var in_buffer = bufValue(value, vbr_bits); |
| 126 | |
| 127 | const continue_bit = @as(@TypeOf(in_buffer), 1) << @intCast(vbr_bits - 1); |
| 128 | const mask = continue_bit - 1; |
| 129 | |
| 130 | // If input is larger than one VBR block can store |
| 131 | // then store vbr_bits - 1 bits and a continue bit |
| 132 | while (in_buffer > mask) { |
| 133 | bits += @intCast(vbr_bits); |
| 134 | in_buffer >>= @intCast(vbr_bits - 1); |
| 135 | } |
| 136 | |
| 137 | // Store remaining bits |
| 138 | bits += @intCast(vbr_bits); |
| 139 | return bits; |
| 140 | } |
| 141 | |
| 142 | pub fn write6BitChar(self: *BcWriter, c: u8) Error!void { |
| 143 | try self.writeBits(charTo6Bit(c), 6); |
| 144 | } |
| 145 | |
| 146 | pub fn alignTo32(self: *BcWriter) Error!void { |
| 147 | if (self.bit_count == 0) return; |
| 148 | |
| 149 | try self.buffer.append(self.bit_buffer); |
| 150 | self.bit_buffer = 0; |
| 151 | self.bit_count = 0; |
| 152 | } |
| 153 | |
| 154 | pub fn enterTopBlock(self: *BcWriter, comptime SubBlock: type) Error!BlockWriter(SubBlock) { |
| 155 | return BlockWriter(SubBlock).init(self, 2); |
| 156 | } |
| 157 | |
| 158 | fn BlockWriter(comptime Block: type) type { |
| 159 | return struct { |
| 160 | const Self = @This(); |
| 161 | |
| 162 | // The minimum abbrev id length based on the number of abbrevs present in the block |
| 163 | pub const abbrev_len = std.math.log2_int_ceil( |
| 164 | u6, |
| 165 | 4 + (if (@hasDecl(Block, "abbrevs")) Block.abbrevs.len else 0), |
| 166 | ); |
| 167 | |
| 168 | start: usize, |
| 169 | bitcode: *BcWriter, |
| 170 | |
| 171 | pub fn init(bitcode: *BcWriter, comptime parent_abbrev_len: u6) Error!Self { |
| 172 | try bitcode.writeBits(1, parent_abbrev_len); |
| 173 | try bitcode.writeVBR(Block.id, 8); |
| 174 | try bitcode.writeVBR(abbrev_len, 4); |
| 175 | try bitcode.alignTo32(); |
| 176 | |
| 177 | // We store the index of the block size and store a dummy value as the number of words in the block |
| 178 | const start = bitcode.length(); |
| 179 | try bitcode.writeBits(0, 32); |
| 180 | |
| 181 | // Predefine all block abbrevs |
| 182 | inline for (Block.abbrevs) |Abbrev| { |
| 183 | try defineAbbrev(bitcode, &Abbrev.ops); |
| 184 | } |
| 185 | |
| 186 | return .{ |
| 187 | .start = start, |
| 188 | .bitcode = bitcode, |
| 189 | }; |
| 190 | } |
| 191 | |
| 192 | pub fn enterSubBlock(self: Self, comptime SubBlock: type) Error!BlockWriter(SubBlock) { |
| 193 | return BlockWriter(SubBlock).init(self.bitcode, abbrev_len); |
| 194 | } |
| 195 | |
| 196 | pub fn end(self: *Self) Error!void { |
| 197 | try self.bitcode.writeBits(0, abbrev_len); |
| 198 | try self.bitcode.alignTo32(); |
| 199 | |
| 200 | // Set the number of words in the block at the start of the block |
| 201 | self.bitcode.buffer.items[self.start] = @truncate(self.bitcode.length() - self.start - 1); |
| 202 | } |
| 203 | |
| 204 | pub fn writeUnabbrev(self: *Self, code: u32, values: []const u64) Error!void { |
| 205 | try self.bitcode.writeBits(3, abbrev_len); |
| 206 | try self.bitcode.writeVBR(code, 6); |
| 207 | try self.bitcode.writeVBR(values.len, 6); |
| 208 | for (values) |val| { |
| 209 | try self.bitcode.writeVBR(val, 6); |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | pub fn writeAbbrev(self: *Self, params: anytype) Error!void { |
| 214 | return self.writeAbbrevAdapted(params, struct { |
| 215 | pub fn get(_: @This(), param: anytype, comptime _: []const u8) @TypeOf(param) { |
| 216 | return param; |
| 217 | } |
| 218 | }{}); |
| 219 | } |
| 220 | |
| 221 | pub fn abbrevId(comptime Abbrev: type) u32 { |
| 222 | inline for (Block.abbrevs, 0..) |abbrev, i| { |
| 223 | if (Abbrev == abbrev) return i + 4; |
| 224 | } |
| 225 | |
| 226 | @compileError("Unknown abbrev: " ++ @typeName(Abbrev)); |
| 227 | } |
| 228 | |
| 229 | pub fn writeAbbrevAdapted( |
| 230 | self: *Self, |
| 231 | params: anytype, |
| 232 | adapter: anytype, |
| 233 | ) Error!void { |
| 234 | const Abbrev = @TypeOf(params); |
| 235 | |
| 236 | try self.bitcode.writeBits(comptime abbrevId(Abbrev), abbrev_len); |
| 237 | |
| 238 | const fields = std.meta.fields(Abbrev); |
| 239 | |
| 240 | // This abbreviation might only contain literals |
| 241 | if (fields.len == 0) return; |
| 242 | |
| 243 | comptime var field_index: usize = 0; |
| 244 | inline for (Abbrev.ops) |ty| { |
| 245 | const field_name = fields[field_index].name; |
| 246 | const param = @field(params, field_name); |
| 247 | |
| 248 | switch (ty) { |
| 249 | .literal => continue, |
| 250 | .fixed => |len| try self.bitcode.writeBits(adapter.get(param, field_name), len), |
| 251 | .fixed_runtime => |width_ty| try self.bitcode.writeBits( |
| 252 | adapter.get(param, field_name), |
| 253 | self.bitcode.widths[getTypeIndex(width_ty)], |
| 254 | ), |
| 255 | .vbr => |len| try self.bitcode.writeVBR(adapter.get(param, field_name), len), |
| 256 | .char6 => try self.bitcode.write6BitChar(adapter.get(param, field_name)), |
| 257 | .blob => { |
| 258 | try self.bitcode.writeVBR(param.len, 6); |
| 259 | try self.bitcode.alignTo32(); |
| 260 | for (param) |x| { |
| 261 | try self.bitcode.writeBits(x, 8); |
| 262 | } |
| 263 | try self.bitcode.alignTo32(); |
| 264 | }, |
| 265 | .array_fixed => |len| { |
| 266 | try self.bitcode.writeVBR(param.len, 6); |
| 267 | for (param) |x| { |
| 268 | try self.bitcode.writeBits(adapter.get(x, field_name), len); |
| 269 | } |
| 270 | }, |
| 271 | .array_fixed_runtime => |width_ty| { |
| 272 | try self.bitcode.writeVBR(param.len, 6); |
| 273 | for (param) |x| { |
| 274 | try self.bitcode.writeBits( |
| 275 | adapter.get(x, field_name), |
| 276 | self.bitcode.widths[getTypeIndex(width_ty)], |
| 277 | ); |
| 278 | } |
| 279 | }, |
| 280 | .array_vbr => |len| { |
| 281 | try self.bitcode.writeVBR(param.len, 6); |
| 282 | for (param) |x| { |
| 283 | try self.bitcode.writeVBR(adapter.get(x, field_name), len); |
| 284 | } |
| 285 | }, |
| 286 | .array_char6 => { |
| 287 | try self.bitcode.writeVBR(param.len, 6); |
| 288 | for (param) |x| { |
| 289 | try self.bitcode.write6BitChar(adapter.get(x, field_name)); |
| 290 | } |
| 291 | }, |
| 292 | } |
| 293 | field_index += 1; |
| 294 | if (field_index == fields.len) break; |
| 295 | } |
| 296 | } |
| 297 | |
| 298 | fn defineAbbrev(bitcode: *BcWriter, comptime ops: []const AbbrevOp) Error!void { |
| 299 | try bitcode.writeBits(2, abbrev_len); |
| 300 | |
| 301 | // ops.len is not accurate because arrays are actually two ops |
| 302 | try bitcode.writeVBR(blk: { |
| 303 | var count: usize = 0; |
| 304 | inline for (ops) |op| { |
| 305 | count += switch (op) { |
| 306 | .literal, .fixed, .fixed_runtime, .vbr, .char6, .blob => 1, |
| 307 | .array_fixed, .array_fixed_runtime, .array_vbr, .array_char6 => 2, |
| 308 | }; |
| 309 | } |
| 310 | break :blk count; |
| 311 | }, 5); |
| 312 | |
| 313 | inline for (ops) |op| { |
| 314 | switch (op) { |
| 315 | .literal => |value| { |
| 316 | try bitcode.writeBits(1, 1); |
| 317 | try bitcode.writeVBR(value, 8); |
| 318 | }, |
| 319 | .fixed => |width| { |
| 320 | try bitcode.writeBits(0, 1); |
| 321 | try bitcode.writeBits(1, 3); |
| 322 | try bitcode.writeVBR(width, 5); |
| 323 | }, |
| 324 | .fixed_runtime => |width_ty| { |
| 325 | try bitcode.writeBits(0, 1); |
| 326 | try bitcode.writeBits(1, 3); |
| 327 | try bitcode.writeVBR(bitcode.widths[getTypeIndex(width_ty)], 5); |
| 328 | }, |
| 329 | .vbr => |width| { |
| 330 | try bitcode.writeBits(0, 1); |
| 331 | try bitcode.writeBits(2, 3); |
| 332 | try bitcode.writeVBR(width, 5); |
| 333 | }, |
| 334 | .char6 => { |
| 335 | try bitcode.writeBits(0, 1); |
| 336 | try bitcode.writeBits(4, 3); |
| 337 | }, |
| 338 | .blob => { |
| 339 | try bitcode.writeBits(0, 1); |
| 340 | try bitcode.writeBits(5, 3); |
| 341 | }, |
| 342 | .array_fixed => |width| { |
| 343 | // Array op |
| 344 | try bitcode.writeBits(0, 1); |
| 345 | try bitcode.writeBits(3, 3); |
| 346 | |
| 347 | // Fixed or VBR op |
| 348 | try bitcode.writeBits(0, 1); |
| 349 | try bitcode.writeBits(1, 3); |
| 350 | try bitcode.writeVBR(width, 5); |
| 351 | }, |
| 352 | .array_fixed_runtime => |width_ty| { |
| 353 | // Array op |
| 354 | try bitcode.writeBits(0, 1); |
| 355 | try bitcode.writeBits(3, 3); |
| 356 | |
| 357 | // Fixed or VBR op |
| 358 | try bitcode.writeBits(0, 1); |
| 359 | try bitcode.writeBits(1, 3); |
| 360 | try bitcode.writeVBR(bitcode.widths[getTypeIndex(width_ty)], 5); |
| 361 | }, |
| 362 | .array_vbr => |width| { |
| 363 | // Array op |
| 364 | try bitcode.writeBits(0, 1); |
| 365 | try bitcode.writeBits(3, 3); |
| 366 | |
| 367 | // Fixed or VBR op |
| 368 | try bitcode.writeBits(0, 1); |
| 369 | try bitcode.writeBits(2, 3); |
| 370 | try bitcode.writeVBR(width, 5); |
| 371 | }, |
| 372 | .array_char6 => { |
| 373 | // Array op |
| 374 | try bitcode.writeBits(0, 1); |
| 375 | try bitcode.writeBits(3, 3); |
| 376 | |
| 377 | // Char6 op |
| 378 | try bitcode.writeBits(0, 1); |
| 379 | try bitcode.writeBits(4, 3); |
| 380 | }, |
| 381 | } |
| 382 | } |
| 383 | } |
| 384 | }; |
| 385 | } |
| 386 | }; |
| 387 | } |
| 388 | |
| 389 | fn charTo6Bit(c: u8) u8 { |
| 390 | return switch (c) { |
| 391 | 'a'...'z' => c - 'a', |
| 392 | 'A'...'Z' => c - 'A' + 26, |
| 393 | '0'...'9' => c - '0' + 52, |
| 394 | '.' => 62, |
| 395 | '_' => 63, |
| 396 | else => @panic("Failed to encode byte as 6-bit char"), |
| 397 | }; |
| 398 | } |
| 399 | |
| 400 | fn BufType(comptime T: type, comptime min_len: usize) type { |
| 401 | return std.meta.Int(.unsigned, @max(min_len, @bitSizeOf(switch (@typeInfo(T)) { |
| 402 | .ComptimeInt => u32, |
| 403 | .Int => |info| if (info.signedness == .unsigned) |
| 404 | T |
| 405 | else |
| 406 | @compileError("Unsupported type: " ++ @typeName(T)), |
| 407 | .Enum => |info| info.tag_type, |
| 408 | .Bool => u1, |
| 409 | .Struct => |info| switch (info.layout) { |
| 410 | .Auto, .Extern => @compileError("Unsupported type: " ++ @typeName(T)), |
| 411 | .Packed => std.meta.Int(.unsigned, @bitSizeOf(T)), |
| 412 | }, |
| 413 | else => @compileError("Unsupported type: " ++ @typeName(T)), |
| 414 | }))); |
| 415 | } |
| 416 | |
| 417 | fn bufValue(value: anytype, comptime min_len: usize) BufType(@TypeOf(value), min_len) { |
| 418 | return switch (@typeInfo(@TypeOf(value))) { |
| 419 | .ComptimeInt, .Int => @intCast(value), |
| 420 | .Enum => @intFromEnum(value), |
| 421 | .Bool => @intFromBool(value), |
| 422 | .Struct => @intCast(@as(std.meta.Int(.unsigned, @bitSizeOf(@TypeOf(value))), @bitCast(value))), |
| 423 | else => unreachable, |
| 424 | }; |
| 425 | } |