mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
431 lines
17 KiB
Zig
431 lines
17 KiB
Zig
const std = @import("../../std.zig");
|
|
|
|
pub const AbbrevOp = union(enum) {
|
|
literal: u32, // 0
|
|
fixed: u16, // 1
|
|
fixed_runtime: type, // 1
|
|
vbr: u16, // 2
|
|
char6: void, // 4
|
|
blob: void, // 5
|
|
array_fixed: u16, // 3, 1
|
|
array_fixed_runtime: type, // 3, 1
|
|
array_vbr: u16, // 3, 2
|
|
array_char6: void, // 3, 4
|
|
};
|
|
|
|
pub const Error = error{OutOfMemory};
|
|
|
|
pub fn BitcodeWriter(comptime types: []const type) type {
|
|
return struct {
|
|
const BcWriter = @This();
|
|
|
|
buffer: std.array_list.Managed(u32),
|
|
bit_buffer: u32 = 0,
|
|
bit_count: u5 = 0,
|
|
|
|
widths: [types.len]u16,
|
|
|
|
pub fn getTypeWidth(self: BcWriter, comptime Type: type) u16 {
|
|
return self.widths[comptime std.mem.findScalar(type, types, Type).?];
|
|
}
|
|
|
|
pub fn init(allocator: std.mem.Allocator, widths: [types.len]u16) BcWriter {
|
|
return .{
|
|
.buffer = std.array_list.Managed(u32).init(allocator),
|
|
.widths = widths,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: BcWriter) void {
|
|
self.buffer.deinit();
|
|
}
|
|
|
|
pub fn toOwnedSlice(self: *BcWriter) Error![]const u32 {
|
|
std.debug.assert(self.bit_count == 0);
|
|
return self.buffer.toOwnedSlice();
|
|
}
|
|
|
|
pub fn length(self: BcWriter) usize {
|
|
std.debug.assert(self.bit_count == 0);
|
|
return self.buffer.items.len;
|
|
}
|
|
|
|
pub fn writeBits(self: *BcWriter, value: anytype, bits: u16) Error!void {
|
|
if (bits == 0) return;
|
|
|
|
var in_buffer = bufValue(value, 32);
|
|
var in_bits = bits;
|
|
|
|
// Store input bits in buffer if they fit otherwise store as many as possible and flush
|
|
if (self.bit_count > 0) {
|
|
const bits_remaining = 31 - self.bit_count + 1;
|
|
const n: u5 = @intCast(@min(bits_remaining, in_bits));
|
|
const v = @as(u32, @truncate(in_buffer)) << self.bit_count;
|
|
self.bit_buffer |= v;
|
|
in_buffer >>= n;
|
|
|
|
self.bit_count +%= n;
|
|
in_bits -= n;
|
|
|
|
if (self.bit_count != 0) return;
|
|
try self.buffer.append(std.mem.nativeToLittle(u32, self.bit_buffer));
|
|
self.bit_buffer = 0;
|
|
}
|
|
|
|
// Write 32-bit chunks of input bits
|
|
while (in_bits >= 32) {
|
|
try self.buffer.append(std.mem.nativeToLittle(u32, @truncate(in_buffer)));
|
|
|
|
in_buffer >>= 31;
|
|
in_buffer >>= 1;
|
|
in_bits -= 32;
|
|
}
|
|
|
|
// Store remaining input bits in buffer
|
|
if (in_bits > 0) {
|
|
self.bit_count = @intCast(in_bits);
|
|
self.bit_buffer = @truncate(in_buffer);
|
|
}
|
|
}
|
|
|
|
pub fn writeVbr(self: *BcWriter, value: anytype, comptime vbr_bits: usize) Error!void {
|
|
comptime {
|
|
std.debug.assert(vbr_bits > 1);
|
|
if (@bitSizeOf(@TypeOf(value)) > 64) @compileError("Unsupported VBR block type: " ++ @typeName(@TypeOf(value)));
|
|
}
|
|
|
|
var in_buffer = bufValue(value, vbr_bits);
|
|
|
|
const continue_bit = @as(@TypeOf(in_buffer), 1) << @intCast(vbr_bits - 1);
|
|
const mask = continue_bit - 1;
|
|
|
|
// If input is larger than one VBR block can store
|
|
// then store vbr_bits - 1 bits and a continue bit
|
|
while (in_buffer > mask) {
|
|
try self.writeBits(in_buffer & mask | continue_bit, vbr_bits);
|
|
in_buffer >>= @intCast(vbr_bits - 1);
|
|
}
|
|
|
|
// Store remaining bits
|
|
try self.writeBits(in_buffer, vbr_bits);
|
|
}
|
|
|
|
pub fn bitsVbr(value: anytype, comptime vbr_bits: usize) u16 {
|
|
comptime {
|
|
std.debug.assert(vbr_bits > 1);
|
|
if (@bitSizeOf(@TypeOf(value)) > 64) @compileError("Unsupported VBR block type: " ++ @typeName(@TypeOf(value)));
|
|
}
|
|
|
|
var bits: u16 = 0;
|
|
|
|
var in_buffer = bufValue(value, vbr_bits);
|
|
|
|
const continue_bit = @as(@TypeOf(in_buffer), 1) << @intCast(vbr_bits - 1);
|
|
const mask = continue_bit - 1;
|
|
|
|
// If input is larger than one VBR block can store
|
|
// then store vbr_bits - 1 bits and a continue bit
|
|
while (in_buffer > mask) {
|
|
bits += @intCast(vbr_bits);
|
|
in_buffer >>= @intCast(vbr_bits - 1);
|
|
}
|
|
|
|
// Store remaining bits
|
|
bits += @intCast(vbr_bits);
|
|
return bits;
|
|
}
|
|
|
|
pub fn write6BitChar(self: *BcWriter, c: u8) Error!void {
|
|
try self.writeBits(charTo6Bit(c), 6);
|
|
}
|
|
|
|
pub fn writeBlob(self: *BcWriter, blob: []const u8) Error!void {
|
|
const blob_word_size = std.mem.alignForward(usize, blob.len, 4);
|
|
try self.buffer.ensureUnusedCapacity(blob_word_size + 1);
|
|
self.alignTo32() catch unreachable;
|
|
|
|
const slice = self.buffer.addManyAsSliceAssumeCapacity(blob_word_size / 4);
|
|
const slice_bytes = std.mem.sliceAsBytes(slice);
|
|
@memcpy(slice_bytes[0..blob.len], blob);
|
|
@memset(slice_bytes[blob.len..], 0);
|
|
}
|
|
|
|
pub fn alignTo32(self: *BcWriter) Error!void {
|
|
if (self.bit_count == 0) return;
|
|
|
|
try self.buffer.append(std.mem.nativeToLittle(u32, self.bit_buffer));
|
|
self.bit_buffer = 0;
|
|
self.bit_count = 0;
|
|
}
|
|
|
|
pub fn enterTopBlock(self: *BcWriter, comptime SubBlock: type) Error!BlockWriter(SubBlock) {
|
|
return BlockWriter(SubBlock).init(self, 2, true);
|
|
}
|
|
|
|
fn BlockWriter(comptime Block: type) type {
|
|
return struct {
|
|
const Self = @This();
|
|
|
|
// The minimum abbrev id length based on the number of abbrevs present in the block
|
|
pub const abbrev_len = std.math.log2_int_ceil(
|
|
u6,
|
|
4 + (if (@hasDecl(Block, "abbrevs")) Block.abbrevs.len else 0),
|
|
);
|
|
|
|
start: usize,
|
|
bitcode: *BcWriter,
|
|
|
|
pub fn init(bitcode: *BcWriter, comptime parent_abbrev_len: u6, comptime define_abbrevs: bool) Error!Self {
|
|
try bitcode.writeBits(1, parent_abbrev_len);
|
|
try bitcode.writeVbr(Block.id, 8);
|
|
try bitcode.writeVbr(abbrev_len, 4);
|
|
try bitcode.alignTo32();
|
|
|
|
// We store the index of the block size and store a dummy value as the number of words in the block
|
|
const start = bitcode.length();
|
|
try bitcode.writeBits(0, 32);
|
|
|
|
var self = Self{
|
|
.start = start,
|
|
.bitcode = bitcode,
|
|
};
|
|
|
|
// Predefine all block abbrevs
|
|
if (define_abbrevs) {
|
|
inline for (Block.abbrevs) |Abbrev| {
|
|
try self.defineAbbrev(&Abbrev.ops);
|
|
}
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
pub fn enterSubBlock(self: Self, comptime SubBlock: type, comptime define_abbrevs: bool) Error!BlockWriter(SubBlock) {
|
|
return BlockWriter(SubBlock).init(self.bitcode, abbrev_len, define_abbrevs);
|
|
}
|
|
|
|
pub fn end(self: *Self) Error!void {
|
|
try self.bitcode.writeBits(0, abbrev_len);
|
|
try self.bitcode.alignTo32();
|
|
|
|
// Set the number of words in the block at the start of the block
|
|
self.bitcode.buffer.items[self.start] = std.mem.nativeToLittle(u32, @truncate(self.bitcode.length() - self.start - 1));
|
|
}
|
|
|
|
pub fn writeUnabbrev(self: *Self, code: u32, values: []const u64) Error!void {
|
|
try self.bitcode.writeBits(3, abbrev_len);
|
|
try self.bitcode.writeVbr(code, 6);
|
|
try self.bitcode.writeVbr(values.len, 6);
|
|
for (values) |val| {
|
|
try self.bitcode.writeVbr(val, 6);
|
|
}
|
|
}
|
|
|
|
pub fn writeAbbrev(self: *Self, params: anytype) Error!void {
|
|
return self.writeAbbrevAdapted(params, struct {
|
|
pub fn get(_: @This(), param: anytype) @TypeOf(param) {
|
|
return param;
|
|
}
|
|
}{});
|
|
}
|
|
|
|
pub fn abbrevId(comptime Abbrev: type) u32 {
|
|
inline for (Block.abbrevs, 0..) |abbrev, i| {
|
|
if (Abbrev == abbrev) return i + 4;
|
|
}
|
|
|
|
@compileError("Unknown abbrev: " ++ @typeName(Abbrev));
|
|
}
|
|
|
|
pub fn writeAbbrevAdapted(
|
|
self: *Self,
|
|
params: anytype,
|
|
adapter: anytype,
|
|
) Error!void {
|
|
const Abbrev = @TypeOf(params);
|
|
|
|
try self.bitcode.writeBits(comptime abbrevId(Abbrev), abbrev_len);
|
|
|
|
const fields = std.meta.fields(Abbrev);
|
|
|
|
// This abbreviation might only contain literals
|
|
if (fields.len == 0) return;
|
|
|
|
comptime var field_index: usize = 0;
|
|
inline for (Abbrev.ops) |ty| {
|
|
const param = @field(params, fields[field_index].name);
|
|
switch (ty) {
|
|
.literal => continue,
|
|
.fixed => |len| try self.bitcode.writeBits(adapter.get(param), len),
|
|
.fixed_runtime => |width_ty| try self.bitcode.writeBits(
|
|
adapter.get(param),
|
|
self.bitcode.getTypeWidth(width_ty),
|
|
),
|
|
.vbr => |len| try self.bitcode.writeVbr(adapter.get(param), len),
|
|
.char6 => try self.bitcode.write6BitChar(adapter.get(param)),
|
|
.blob => {
|
|
try self.bitcode.writeVbr(param.len, 6);
|
|
try self.bitcode.writeBlob(param);
|
|
},
|
|
.array_fixed => |len| {
|
|
try self.bitcode.writeVbr(param.len, 6);
|
|
for (param) |x| {
|
|
try self.bitcode.writeBits(adapter.get(x), len);
|
|
}
|
|
},
|
|
.array_fixed_runtime => |width_ty| {
|
|
try self.bitcode.writeVbr(param.len, 6);
|
|
for (param) |x| {
|
|
try self.bitcode.writeBits(
|
|
adapter.get(x),
|
|
self.bitcode.getTypeWidth(width_ty),
|
|
);
|
|
}
|
|
},
|
|
.array_vbr => |len| {
|
|
try self.bitcode.writeVbr(param.len, 6);
|
|
for (param) |x| {
|
|
try self.bitcode.writeVbr(adapter.get(x), len);
|
|
}
|
|
},
|
|
.array_char6 => {
|
|
try self.bitcode.writeVbr(param.len, 6);
|
|
for (param) |x| {
|
|
try self.bitcode.write6BitChar(adapter.get(x));
|
|
}
|
|
},
|
|
}
|
|
field_index += 1;
|
|
if (field_index == fields.len) break;
|
|
}
|
|
}
|
|
|
|
pub fn defineAbbrev(self: *Self, comptime ops: []const AbbrevOp) Error!void {
|
|
const bitcode = self.bitcode;
|
|
try bitcode.writeBits(2, abbrev_len);
|
|
|
|
// ops.len is not accurate because arrays are actually two ops
|
|
try bitcode.writeVbr(blk: {
|
|
var count: usize = 0;
|
|
inline for (ops) |op| {
|
|
count += switch (op) {
|
|
.literal, .fixed, .fixed_runtime, .vbr, .char6, .blob => 1,
|
|
.array_fixed, .array_fixed_runtime, .array_vbr, .array_char6 => 2,
|
|
};
|
|
}
|
|
break :blk count;
|
|
}, 5);
|
|
|
|
inline for (ops) |op| {
|
|
switch (op) {
|
|
.literal => |value| {
|
|
try bitcode.writeBits(1, 1);
|
|
try bitcode.writeVbr(value, 8);
|
|
},
|
|
.fixed => |width| {
|
|
try bitcode.writeBits(0, 1);
|
|
try bitcode.writeBits(1, 3);
|
|
try bitcode.writeVbr(width, 5);
|
|
},
|
|
.fixed_runtime => |width_ty| {
|
|
try bitcode.writeBits(0, 1);
|
|
try bitcode.writeBits(1, 3);
|
|
try bitcode.writeVbr(bitcode.getTypeWidth(width_ty), 5);
|
|
},
|
|
.vbr => |width| {
|
|
try bitcode.writeBits(0, 1);
|
|
try bitcode.writeBits(2, 3);
|
|
try bitcode.writeVbr(width, 5);
|
|
},
|
|
.char6 => {
|
|
try bitcode.writeBits(0, 1);
|
|
try bitcode.writeBits(4, 3);
|
|
},
|
|
.blob => {
|
|
try bitcode.writeBits(0, 1);
|
|
try bitcode.writeBits(5, 3);
|
|
},
|
|
.array_fixed => |width| {
|
|
// Array op
|
|
try bitcode.writeBits(0, 1);
|
|
try bitcode.writeBits(3, 3);
|
|
|
|
// Fixed or VBR op
|
|
try bitcode.writeBits(0, 1);
|
|
try bitcode.writeBits(1, 3);
|
|
try bitcode.writeVbr(width, 5);
|
|
},
|
|
.array_fixed_runtime => |width_ty| {
|
|
// Array op
|
|
try bitcode.writeBits(0, 1);
|
|
try bitcode.writeBits(3, 3);
|
|
|
|
// Fixed or VBR op
|
|
try bitcode.writeBits(0, 1);
|
|
try bitcode.writeBits(1, 3);
|
|
try bitcode.writeVbr(bitcode.getTypeWidth(width_ty), 5);
|
|
},
|
|
.array_vbr => |width| {
|
|
// Array op
|
|
try bitcode.writeBits(0, 1);
|
|
try bitcode.writeBits(3, 3);
|
|
|
|
// Fixed or VBR op
|
|
try bitcode.writeBits(0, 1);
|
|
try bitcode.writeBits(2, 3);
|
|
try bitcode.writeVbr(width, 5);
|
|
},
|
|
.array_char6 => {
|
|
// Array op
|
|
try bitcode.writeBits(0, 1);
|
|
try bitcode.writeBits(3, 3);
|
|
|
|
// Char6 op
|
|
try bitcode.writeBits(0, 1);
|
|
try bitcode.writeBits(4, 3);
|
|
},
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
};
|
|
}
|
|
|
|
fn charTo6Bit(c: u8) u8 {
|
|
return switch (c) {
|
|
'a'...'z' => c - 'a',
|
|
'A'...'Z' => c - 'A' + 26,
|
|
'0'...'9' => c - '0' + 52,
|
|
'.' => 62,
|
|
'_' => 63,
|
|
else => @panic("Failed to encode byte as 6-bit char"),
|
|
};
|
|
}
|
|
|
|
fn BufType(comptime T: type, comptime min_len: usize) type {
|
|
return std.meta.Int(.unsigned, @max(min_len, @bitSizeOf(switch (@typeInfo(T)) {
|
|
.comptime_int => u32,
|
|
.int => |info| if (info.signedness == .unsigned)
|
|
T
|
|
else
|
|
@compileError("Unsupported type: " ++ @typeName(T)),
|
|
.@"enum" => |info| info.tag_type,
|
|
.bool => u1,
|
|
.@"struct" => |info| switch (info.layout) {
|
|
.auto, .@"extern" => @compileError("Unsupported type: " ++ @typeName(T)),
|
|
.@"packed" => std.meta.Int(.unsigned, @bitSizeOf(T)),
|
|
},
|
|
else => @compileError("Unsupported type: " ++ @typeName(T)),
|
|
})));
|
|
}
|
|
|
|
fn bufValue(value: anytype, comptime min_len: usize) BufType(@TypeOf(value), min_len) {
|
|
return switch (@typeInfo(@TypeOf(value))) {
|
|
.comptime_int, .int => @intCast(value),
|
|
.@"enum" => @intFromEnum(value),
|
|
.bool => @intFromBool(value),
|
|
.@"struct" => @intCast(@as(std.meta.Int(.unsigned, @bitSizeOf(@TypeOf(value))), @bitCast(value))),
|
|
else => unreachable,
|
|
};
|
|
}
|