diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Compilation.zig | 389 | ||||
| -rw-r--r-- | src/codegen/llvm/BitcodeReader.zig | 515 |
2 files changed, 840 insertions, 64 deletions
diff --git a/src/Compilation.zig b/src/Compilation.zig index 974861690e..3bd7d1506c 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -77,7 +77,7 @@ embed_file_work_queue: std.fifo.LinearFifo(*Module.EmbedFile, .Dynamic), /// The ErrorMsg memory is owned by the `CObject`, using Compilation's general purpose allocator. /// This data is accessed by multiple threads and is protected by `mutex`. -failed_c_objects: std.AutoArrayHashMapUnmanaged(*CObject, *CObject.ErrorMsg) = .{}, +failed_c_objects: std.AutoArrayHashMapUnmanaged(*CObject, *CObject.Diag.Bundle) = .{}, /// The ErrorBundle memory is owned by the `Win32Resource`, using Compilation's general purpose allocator. /// This data is accessed by multiple threads and is protected by `mutex`. @@ -318,15 +318,286 @@ pub const CObject = struct { failure_retryable, }, - pub const ErrorMsg = struct { - msg: []const u8, - line: u32, - column: u32, + pub const Diag = struct { + level: u32 = 0, + category: u32 = 0, + msg: []const u8 = &.{}, + src_loc: SrcLoc = .{}, + src_ranges: []const SrcRange = &.{}, + sub_diags: []const Diag = &.{}, + + pub const SrcLoc = struct { + file: u32 = 0, + line: u32 = 0, + column: u32 = 0, + offset: u32 = 0, + }; + + pub const SrcRange = struct { + start: SrcLoc = .{}, + end: SrcLoc = .{}, + }; + + pub fn deinit(diag: *Diag, gpa: Allocator) void { + gpa.free(diag.msg); + gpa.free(diag.src_ranges); + for (diag.sub_diags) |sub_diag| { + var sub_diag_mut = sub_diag; + sub_diag_mut.deinit(gpa); + } + gpa.free(diag.sub_diags); + diag.* = undefined; + } - pub fn destroy(em: *ErrorMsg, gpa: Allocator) void { - gpa.free(em.msg); - gpa.destroy(em); + pub fn count(diag: Diag) u32 { + var total: u32 = 1; + for (diag.sub_diags) |sub_diag| total += sub_diag.count(); + return total; } + + pub fn addToErrorBundle(diag: Diag, eb: *ErrorBundle.Wip, bundle: Bundle, note: *u32) !void { + const err_msg = try eb.addErrorMessage(try diag.toErrorMessage(eb, bundle, 0)); + eb.extra.items[note.*] = @intFromEnum(err_msg); + note.* += 1; + for (diag.sub_diags) |sub_diag| try sub_diag.addToErrorBundle(eb, bundle, note); + } + + pub fn toErrorMessage( + diag: Diag, + eb: *ErrorBundle.Wip, + bundle: Bundle, + notes_len: u32, + ) !ErrorBundle.ErrorMessage { + var start = diag.src_loc.offset; + var end = diag.src_loc.offset; + for (diag.src_ranges) |src_range| { + if (src_range.start.file == diag.src_loc.file and + src_range.start.line == diag.src_loc.line) + { + start = @min(src_range.start.offset, start); + } + if (src_range.end.file == diag.src_loc.file and + src_range.end.line == diag.src_loc.line) + { + end = @max(src_range.end.offset, end); + } + } + + const file_name = bundle.file_names.get(diag.src_loc.file) orelse ""; + const source_line = source_line: { + if (diag.src_loc.offset == 0 or diag.src_loc.column == 0) break :source_line 0; + + const file = std.fs.cwd().openFile(file_name, .{}) catch break :source_line 0; + defer file.close(); + file.seekTo(diag.src_loc.offset + 1 - diag.src_loc.column) catch break :source_line 0; + + var line = std.ArrayList(u8).init(eb.gpa); + defer line.deinit(); + file.reader().readUntilDelimiterArrayList(&line, '\n', 1 << 10) catch break :source_line 0; + + break :source_line try eb.addString(line.items); + }; + + return .{ + .msg = try eb.addString(diag.msg), + .src_loc = try eb.addSourceLocation(.{ + .src_path = try eb.addString(file_name), + .line = diag.src_loc.line -| 1, + .column = diag.src_loc.column -| 1, + .span_start = start, + .span_main = diag.src_loc.offset, + .span_end = end + 1, + .source_line = source_line, + }), + .notes_len = notes_len, + }; + } + + pub const Bundle = struct { + file_names: std.AutoHashMapUnmanaged(u32, []const u8) = .{}, + category_names: std.AutoHashMapUnmanaged(u32, []const u8) = .{}, + diags: []Diag = &.{}, + + pub fn destroy(bundle: *Bundle, gpa: Allocator) void { + var file_name_it = bundle.file_names.valueIterator(); + while (file_name_it.next()) |file_name| gpa.free(file_name.*); + bundle.file_names.deinit(gpa); + + var category_name_it = bundle.category_names.valueIterator(); + while (category_name_it.next()) |category_name| gpa.free(category_name.*); + bundle.category_names.deinit(gpa); + + for (bundle.diags) |*diag| diag.deinit(gpa); + gpa.free(bundle.diags); + + gpa.destroy(bundle); + } + + pub fn parse(gpa: Allocator, path: []const u8) !*Bundle { + const BitcodeReader = @import("codegen/llvm/BitcodeReader.zig"); + const BlockId = enum(u32) { + Meta = 8, + Diag, + _, + }; + const RecordId = enum(u32) { + Version = 1, + DiagInfo, + SrcRange, + DiagFlag, + CatName, + FileName, + FixIt, + _, + }; + const WipDiag = struct { + level: u32 = 0, + category: u32 = 0, + msg: []const u8 = &.{}, + src_loc: SrcLoc = .{}, + src_ranges: std.ArrayListUnmanaged(SrcRange) = .{}, + sub_diags: std.ArrayListUnmanaged(Diag) = .{}, + + fn deinit(wip_diag: *@This(), allocator: Allocator) void { + allocator.free(wip_diag.msg); + wip_diag.src_ranges.deinit(allocator); + for (wip_diag.sub_diags.items) |*sub_diag| sub_diag.deinit(allocator); + wip_diag.sub_diags.deinit(allocator); + wip_diag.* = undefined; + } + }; + + const file = try std.fs.cwd().openFile(path, .{}); + defer file.close(); + var br = std.io.bufferedReader(file.reader()); + const reader = br.reader(); + var bc = BitcodeReader.init(gpa, .{ .reader = reader.any() }); + defer bc.deinit(); + + var file_names: std.AutoHashMapUnmanaged(u32, []const u8) = .{}; + errdefer { + var file_name_it = file_names.valueIterator(); + while (file_name_it.next()) |file_name| gpa.free(file_name.*); + file_names.deinit(gpa); + } + + var category_names: std.AutoHashMapUnmanaged(u32, []const u8) = .{}; + errdefer { + var category_name_it = category_names.valueIterator(); + while (category_name_it.next()) |category_name| gpa.free(category_name.*); + category_names.deinit(gpa); + } + + var stack: std.ArrayListUnmanaged(WipDiag) = .{}; + defer { + for (stack.items) |*wip_diag| wip_diag.deinit(gpa); + stack.deinit(gpa); + } + try stack.append(gpa, .{}); + + try bc.checkMagic("DIAG"); + while (try bc.next()) |item| switch (item) { + .start_block => |block| switch (@as(BlockId, @enumFromInt(block.id))) { + .Meta => if (stack.items.len > 0) try bc.skipBlock(block), + .Diag => try stack.append(gpa, .{}), + _ => try bc.skipBlock(block), + }, + .record => |record| switch (@as(RecordId, @enumFromInt(record.id))) { + .Version => if (record.operands[0] != 2) return error.InvalidVersion, + .DiagInfo => { + const top = &stack.items[stack.items.len - 1]; + top.level = @intCast(record.operands[0]); + top.src_loc = .{ + .file = @intCast(record.operands[1]), + .line = @intCast(record.operands[2]), + .column = @intCast(record.operands[3]), + .offset = @intCast(record.operands[4]), + }; + top.category = @intCast(record.operands[5]); + top.msg = try gpa.dupe(u8, record.blob); + }, + .SrcRange => try stack.items[stack.items.len - 1].src_ranges.append(gpa, .{ + .start = .{ + .file = @intCast(record.operands[0]), + .line = @intCast(record.operands[1]), + .column = @intCast(record.operands[2]), + .offset = @intCast(record.operands[3]), + }, + .end = .{ + .file = @intCast(record.operands[4]), + .line = @intCast(record.operands[5]), + .column = @intCast(record.operands[6]), + .offset = @intCast(record.operands[7]), + }, + }), + .DiagFlag => {}, + .CatName => { + try category_names.ensureUnusedCapacity(gpa, 1); + category_names.putAssumeCapacity( + @intCast(record.operands[0]), + try gpa.dupe(u8, record.blob), + ); + }, + .FileName => { + try file_names.ensureUnusedCapacity(gpa, 1); + file_names.putAssumeCapacity( + @intCast(record.operands[0]), + try gpa.dupe(u8, record.blob), + ); + }, + .FixIt => {}, + _ => {}, + }, + .end_block => |block| switch (@as(BlockId, @enumFromInt(block.id))) { + .Meta => {}, + .Diag => { + var wip_diag = stack.pop(); + errdefer wip_diag.deinit(gpa); + + const src_ranges = try wip_diag.src_ranges.toOwnedSlice(gpa); + errdefer gpa.free(src_ranges); + + const sub_diags = try wip_diag.sub_diags.toOwnedSlice(gpa); + errdefer { + for (sub_diags) |*sub_diag| sub_diag.deinit(gpa); + gpa.free(sub_diags); + } + + try stack.items[stack.items.len - 1].sub_diags.append(gpa, .{ + .level = wip_diag.level, + .category = wip_diag.category, + .msg = wip_diag.msg, + .src_loc = wip_diag.src_loc, + .src_ranges = src_ranges, + .sub_diags = sub_diags, + }); + }, + _ => {}, + }, + }; + + const bundle = try gpa.create(Bundle); + assert(stack.items.len == 1); + bundle.* = .{ + .file_names = file_names, + .category_names = category_names, + .diags = try stack.items[0].sub_diags.toOwnedSlice(gpa), + }; + return bundle; + } + + pub fn addToErrorBundle(bundle: Bundle, eb: *ErrorBundle.Wip) !void { + for (bundle.diags) |diag| { + const notes_len = diag.count() - 1; + try eb.addRootErrorMessage(try diag.toErrorMessage(eb, bundle, notes_len)); + if (notes_len > 0) { + var note = try eb.reserveNotes(notes_len); + for (diag.sub_diags) |sub_diag| + try sub_diag.addToErrorBundle(eb, bundle, ¬e); + } + } + } + }; }; /// Returns if there was failure. @@ -2826,11 +3097,16 @@ fn addBuf(bufs_list: []std.os.iovec_const, bufs_len: *usize, buf: []const u8) vo /// This function is temporally single-threaded. pub fn totalErrorCount(self: *Compilation) u32 { - var total: usize = self.failed_c_objects.count() + + var total: usize = self.misc_failures.count() + @intFromBool(self.alloc_failure_occurred) + self.lld_errors.items.len; + { + var it = self.failed_c_objects.iterator(); + while (it.next()) |entry| total += entry.value_ptr.*.diags.len; + } + if (!build_options.only_core_functionality) { for (self.failed_win32_resources.values()) |errs| { total += errs.errorMessageCount(); @@ -2911,24 +3187,7 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle { { var it = self.failed_c_objects.iterator(); - while (it.next()) |entry| { - const c_object = entry.key_ptr.*; - const err_msg = entry.value_ptr.*; - // TODO these fields will need to be adjusted when we have proper - // C error reporting bubbling up. - try bundle.addRootErrorMessage(.{ - .msg = try bundle.printString("unable to build C object: {s}", .{err_msg.msg}), - .src_loc = try bundle.addSourceLocation(.{ - .src_path = try bundle.addString(c_object.src.src_path), - .span_start = 0, - .span_main = 0, - .span_end = 1, - .line = err_msg.line, - .column = err_msg.column, - .source_line = 0, // TODO - }), - }); - } + while (it.next()) |entry| try entry.value_ptr.*.addToErrorBundle(&bundle); } if (!build_options.only_core_functionality) { @@ -4209,19 +4468,9 @@ fn reportRetryableCObjectError( ) error{OutOfMemory}!void { c_object.status = .failure_retryable; - const c_obj_err_msg = try comp.gpa.create(CObject.ErrorMsg); - errdefer comp.gpa.destroy(c_obj_err_msg); - const msg = try std.fmt.allocPrint(comp.gpa, "{s}", .{@errorName(err)}); - errdefer comp.gpa.free(msg); - c_obj_err_msg.* = .{ - .msg = msg, - .line = 0, - .column = 0, - }; - { - comp.mutex.lock(); - defer comp.mutex.unlock(); - try comp.failed_c_objects.putNoClobber(comp.gpa, c_object, c_obj_err_msg); + switch (comp.failCObj(c_object, "{s}", .{@errorName(err)})) { + error.AnalysisFail => return, + else => |e| return e, } } @@ -4457,6 +4706,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P // We can't know the digest until we do the C compiler invocation, // so we need a temporary filename. const out_obj_path = try comp.tmpFilePath(arena, o_basename); + const out_diag_path = try std.fmt.allocPrint(arena, "{s}.diag", .{out_obj_path}); var zig_cache_tmp_dir = try comp.local_cache_directory.handle.makeOpenPath("tmp", .{}); defer zig_cache_tmp_dir.close(); @@ -4470,18 +4720,20 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P try argv.ensureUnusedCapacity(5); switch (comp.clang_preprocessor_mode) { - .no => argv.appendSliceAssumeCapacity(&[_][]const u8{ "-c", "-o", out_obj_path }), - .yes => argv.appendSliceAssumeCapacity(&[_][]const u8{ "-E", "-o", out_obj_path }), + .no => argv.appendSliceAssumeCapacity(&.{ "-c", "-o", out_obj_path }), + .yes => argv.appendSliceAssumeCapacity(&.{ "-E", "-o", out_obj_path }), .stdout => argv.appendAssumeCapacity("-E"), } if (comp.clang_passthrough_mode) { if (comp.emit_asm != null) { argv.appendAssumeCapacity("-S"); } else if (comp.emit_llvm_ir != null) { - argv.appendSliceAssumeCapacity(&[_][]const u8{ "-emit-llvm", "-S" }); + argv.appendSliceAssumeCapacity(&.{ "-emit-llvm", "-S" }); } else if (comp.emit_llvm_bc != null) { argv.appendAssumeCapacity("-emit-llvm"); } + } else { + argv.appendSliceAssumeCapacity(&.{ "--serialize-diagnostics", out_diag_path }); } if (comp.verbose_cc) { @@ -4524,10 +4776,11 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P switch (term) { .Exited => |code| { if (code != 0) { - // TODO parse clang stderr and turn it into an error message - // and then call failCObjWithOwnedErrorMsg - log.err("clang failed with stderr: {s}", .{stderr}); - return comp.failCObj(c_object, "clang exited with code {d}", .{code}); + const bundle = CObject.Diag.Bundle.parse(comp.gpa, out_diag_path) catch |err| { + log.err("{}: failed to parse clang diagnostics: {s}", .{ err, stderr }); + return comp.failCObj(c_object, "clang exited with code {d}", .{code}); + }; + return comp.failCObjWithOwnedDiagBundle(c_object, bundle); } }, else => { @@ -5413,37 +5666,45 @@ pub fn addCCArgs( try argv.appendSlice(comp.clang_argv); } -fn failCObj(comp: *Compilation, c_object: *CObject, comptime format: []const u8, args: anytype) SemaError { +fn failCObj( + comp: *Compilation, + c_object: *CObject, + comptime format: []const u8, + args: anytype, +) SemaError { @setCold(true); - const err_msg = blk: { - const msg = try std.fmt.allocPrint(comp.gpa, format, args); - errdefer comp.gpa.free(msg); - const err_msg = try comp.gpa.create(CObject.ErrorMsg); - errdefer comp.gpa.destroy(err_msg); - err_msg.* = .{ - .msg = msg, - .line = 0, - .column = 0, - }; - break :blk err_msg; + const diag_bundle = blk: { + const diag_bundle = try comp.gpa.create(CObject.Diag.Bundle); + diag_bundle.* = .{}; + errdefer diag_bundle.destroy(comp.gpa); + + try diag_bundle.file_names.ensureTotalCapacity(comp.gpa, 1); + diag_bundle.file_names.putAssumeCapacity(1, try comp.gpa.dupe(u8, c_object.src.src_path)); + + diag_bundle.diags = try comp.gpa.alloc(CObject.Diag, 1); + diag_bundle.diags[0] = .{}; + diag_bundle.diags[0].level = 3; + diag_bundle.diags[0].msg = try std.fmt.allocPrint(comp.gpa, format, args); + diag_bundle.diags[0].src_loc.file = 1; + break :blk diag_bundle; }; - return comp.failCObjWithOwnedErrorMsg(c_object, err_msg); + return comp.failCObjWithOwnedDiagBundle(c_object, diag_bundle); } -fn failCObjWithOwnedErrorMsg( +fn failCObjWithOwnedDiagBundle( comp: *Compilation, c_object: *CObject, - err_msg: *CObject.ErrorMsg, + diag_bundle: *CObject.Diag.Bundle, ) SemaError { @setCold(true); { comp.mutex.lock(); defer comp.mutex.unlock(); { - errdefer err_msg.destroy(comp.gpa); + errdefer diag_bundle.destroy(comp.gpa); try comp.failed_c_objects.ensureUnusedCapacity(comp.gpa, 1); } - comp.failed_c_objects.putAssumeCapacityNoClobber(c_object, err_msg); + comp.failed_c_objects.putAssumeCapacityNoClobber(c_object, diag_bundle); } c_object.status = .failure; return error.AnalysisFail; diff --git a/src/codegen/llvm/BitcodeReader.zig b/src/codegen/llvm/BitcodeReader.zig new file mode 100644 index 0000000000..b1e1b7c453 --- /dev/null +++ b/src/codegen/llvm/BitcodeReader.zig @@ -0,0 +1,515 @@ +allocator: std.mem.Allocator, +record_arena: std.heap.ArenaAllocator.State, +reader: std.io.AnyReader, +keep_names: bool, +bit_buffer: u32, +bit_offset: u5, +stack: std.ArrayListUnmanaged(State), +block_info: std.AutoHashMapUnmanaged(u32, Block.Info), + +pub const Item = union(enum) { + start_block: Block, + record: Record, + end_block: Block, +}; + +pub const Block = struct { + name: []const u8, + id: u32, + len: u32, + + const block_info: u32 = 0; + const first_reserved: u32 = 1; + const last_standard: u32 = 7; + + const Info = struct { + block_name: []const u8, + record_names: std.AutoHashMapUnmanaged(u32, []const u8), + abbrevs: Abbrev.Store, + + const default: Info = .{ + .block_name = &.{}, + .record_names = .{}, + .abbrevs = .{ .abbrevs = .{} }, + }; + + const set_bid: u32 = 1; + const block_name: u32 = 2; + const set_record_name: u32 = 3; + + fn deinit(info: *Info, allocator: std.mem.Allocator) void { + allocator.free(info.block_name); + var record_names_it = info.record_names.valueIterator(); + while (record_names_it.next()) |record_name| allocator.free(record_name.*); + info.record_names.deinit(allocator); + info.abbrevs.deinit(allocator); + info.* = undefined; + } + }; +}; + +pub const Record = struct { + name: []const u8, + id: u32, + operands: []u64, + blob: []u8, + + fn toOwnedAbbrev(record: Record, allocator: std.mem.Allocator) !Abbrev { + var operands = std.ArrayList(Abbrev.Operand).init(allocator); + defer operands.deinit(); + + assert(record.id == Abbrev.Builtin.define_abbrev.toRecordId()); + var i: usize = 0; + while (i < record.operands.len) switch (record.operands[i]) { + Abbrev.Operand.literal => { + try operands.append(.{ .literal = record.operands[i + 1] }); + i += 2; + }, + @intFromEnum(Abbrev.Operand.Encoding.fixed) => { + try operands.append(.{ .encoding = .{ .fixed = @intCast(record.operands[i + 1]) } }); + i += 2; + }, + @intFromEnum(Abbrev.Operand.Encoding.vbr) => { + try operands.append(.{ .encoding = .{ .vbr = @intCast(record.operands[i + 1]) } }); + i += 2; + }, + @intFromEnum(Abbrev.Operand.Encoding.array) => { + try operands.append(.{ .encoding = .{ .array = 6 } }); + i += 1; + }, + @intFromEnum(Abbrev.Operand.Encoding.char6) => { + try operands.append(.{ .encoding = .char6 }); + i += 1; + }, + @intFromEnum(Abbrev.Operand.Encoding.blob) => { + try operands.append(.{ .encoding = .{ .blob = 6 } }); + i += 1; + }, + else => unreachable, + }; + + return .{ .operands = try operands.toOwnedSlice() }; + } +}; + +pub const InitOptions = struct { + reader: std.io.AnyReader, + keep_names: bool = false, +}; +pub fn init(allocator: std.mem.Allocator, options: InitOptions) BitcodeReader { + return .{ + .allocator = allocator, + .record_arena = .{}, + .reader = options.reader, + .keep_names = options.keep_names, + .bit_buffer = 0, + .bit_offset = 0, + .stack = .{}, + .block_info = .{}, + }; +} + +pub fn deinit(bc: *BitcodeReader) void { + var block_info_it = bc.block_info.valueIterator(); + while (block_info_it.next()) |block_info| block_info.deinit(bc.allocator); + bc.block_info.deinit(bc.allocator); + for (bc.stack.items) |*state| state.deinit(bc.allocator); + bc.stack.deinit(bc.allocator); + bc.record_arena.promote(bc.allocator).deinit(); + bc.* = undefined; +} + +pub fn checkMagic(bc: *BitcodeReader, magic: *const [4]u8) !void { + var buffer: [4]u8 = undefined; + try bc.readBytes(&buffer); + if (!std.mem.eql(u8, &buffer, magic)) return error.InvalidMagic; + + try bc.startBlock(null, 2); + try bc.block_info.put(bc.allocator, Block.block_info, Block.Info.default); +} + +pub fn next(bc: *BitcodeReader) !?Item { + while (true) { + const record = (try bc.nextRecord()) orelse + return if (bc.stack.items.len > 1) error.EndOfStream else null; + switch (record.id) { + else => return .{ .record = record }, + Abbrev.Builtin.end_block.toRecordId() => { + const block_id = bc.stack.items[bc.stack.items.len - 1].block_id.?; + try bc.endBlock(); + return .{ .end_block = .{ + .name = if (bc.block_info.get(block_id)) |block_info| + block_info.block_name + else + &.{}, + .id = block_id, + .len = 0, + } }; + }, + Abbrev.Builtin.enter_subblock.toRecordId() => { + const block_id: u32 = @intCast(record.operands[0]); + switch (block_id) { + Block.block_info => try bc.parseBlockInfoBlock(), + Block.first_reserved...Block.last_standard => return error.UnsupportedBlockId, + else => { + try bc.startBlock(block_id, @intCast(record.operands[1])); + return .{ .start_block = .{ + .name = if (bc.block_info.get(block_id)) |block_info| + block_info.block_name + else + &.{}, + .id = block_id, + .len = @intCast(record.operands[2]), + } }; + }, + } + }, + Abbrev.Builtin.define_abbrev.toRecordId() => try bc.stack.items[bc.stack.items.len - 1] + .abbrevs.addOwnedAbbrev(bc.allocator, try record.toOwnedAbbrev(bc.allocator)), + } + } +} + +pub fn skipBlock(bc: *BitcodeReader, block: Block) !void { + assert(bc.bit_offset == 0); + try bc.reader.skipBytes(@as(u34, block.len) * 4, .{}); + try bc.endBlock(); +} + +fn nextRecord(bc: *BitcodeReader) !?Record { + const state = &bc.stack.items[bc.stack.items.len - 1]; + const abbrev_id = bc.readFixed(u32, state.abbrev_id_width) catch |err| switch (err) { + error.EndOfStream => return null, + else => |e| return e, + }; + if (abbrev_id >= state.abbrevs.abbrevs.items.len) return error.InvalidAbbrevId; + const abbrev = state.abbrevs.abbrevs.items[abbrev_id]; + + var record_arena = bc.record_arena.promote(bc.allocator); + defer bc.record_arena = record_arena.state; + _ = record_arena.reset(.retain_capacity); + + var operands = try std.ArrayList(u64).initCapacity(record_arena.allocator(), abbrev.operands.len); + var blob = std.ArrayList(u8).init(record_arena.allocator()); + for (abbrev.operands, 0..) |abbrev_operand, abbrev_operand_i| switch (abbrev_operand) { + .literal => |value| operands.appendAssumeCapacity(value), + .encoding => |abbrev_encoding| switch (abbrev_encoding) { + .fixed => |width| operands.appendAssumeCapacity(try bc.readFixed(u64, width)), + .vbr => |width| operands.appendAssumeCapacity(try bc.readVbr(u64, width)), + .array => |len_width| { + assert(abbrev_operand_i + 2 == abbrev.operands.len); + const len: usize = @intCast(try bc.readVbr(u32, len_width)); + try operands.ensureUnusedCapacity(len); + for (0..len) |_| switch (abbrev.operands[abbrev.operands.len - 1]) { + .literal => |elem_value| operands.appendAssumeCapacity(elem_value), + .encoding => |elem_encoding| switch (elem_encoding) { + .fixed => |elem_width| operands.appendAssumeCapacity(try bc.readFixed(u64, elem_width)), + .vbr => |elem_width| operands.appendAssumeCapacity(try bc.readVbr(u64, elem_width)), + .array, .blob => return error.InvalidArrayElement, + .char6 => operands.appendAssumeCapacity(try bc.readChar6()), + }, + .align_32_bits, .block_len => return error.UnsupportedArrayElement, + .abbrev_op => switch (try bc.readFixed(u1, 1)) { + 1 => try operands.appendSlice(&.{ + Abbrev.Operand.literal, + try bc.readVbr(u64, 8), + }), + 0 => { + const encoding: Abbrev.Operand.Encoding = + @enumFromInt(try bc.readFixed(u3, 3)); + try operands.append(@intFromEnum(encoding)); + switch (encoding) { + .fixed, .vbr => try operands.append(try bc.readVbr(u7, 5)), + .array, .char6, .blob => {}, + _ => return error.UnsuportedAbbrevEncoding, + } + }, + }, + }; + break; + }, + .char6 => operands.appendAssumeCapacity(try bc.readChar6()), + .blob => |len_width| { + assert(abbrev_operand_i + 1 == abbrev.operands.len); + const len = std.math.cast(usize, try bc.readVbr(u32, len_width)) orelse + return error.Overflow; + bc.align32Bits(); + try bc.readBytes(try blob.addManyAsSlice(len)); + bc.align32Bits(); + }, + }, + .align_32_bits => bc.align32Bits(), + .block_len => operands.appendAssumeCapacity(try bc.read32Bits()), + .abbrev_op => unreachable, + }; + return .{ + .name = name: { + if (operands.items.len < 1) break :name &.{}; + const record_id = std.math.cast(u32, operands.items[0]) orelse break :name &.{}; + if (state.block_id) |block_id| { + if (bc.block_info.get(block_id)) |block_info| { + break :name block_info.record_names.get(record_id) orelse break :name &.{}; + } + } + break :name &.{}; + }, + .id = std.math.cast(u32, operands.items[0]) orelse return error.InvalidRecordId, + .operands = operands.items[1..], + .blob = blob.items, + }; +} + +fn startBlock(bc: *BitcodeReader, block_id: ?u32, new_abbrev_len: u6) !void { + const abbrevs = if (block_id) |id| + if (bc.block_info.get(id)) |block_info| block_info.abbrevs.abbrevs.items else &.{} + else + &.{}; + + const state = try bc.stack.addOne(bc.allocator); + state.* = .{ + .block_id = block_id, + .abbrev_id_width = new_abbrev_len, + .abbrevs = .{ .abbrevs = .{} }, + }; + try state.abbrevs.abbrevs.ensureTotalCapacity( + bc.allocator, + @typeInfo(Abbrev.Builtin).Enum.fields.len + abbrevs.len, + ); + + assert(state.abbrevs.abbrevs.items.len == @intFromEnum(Abbrev.Builtin.end_block)); + try state.abbrevs.addAbbrevAssumeCapacity(bc.allocator, .{ + .operands = &.{ + .{ .literal = Abbrev.Builtin.end_block.toRecordId() }, + .align_32_bits, + }, + }); + assert(state.abbrevs.abbrevs.items.len == @intFromEnum(Abbrev.Builtin.enter_subblock)); + try state.abbrevs.addAbbrevAssumeCapacity(bc.allocator, .{ + .operands = &.{ + .{ .literal = Abbrev.Builtin.enter_subblock.toRecordId() }, + .{ .encoding = .{ .vbr = 8 } }, // blockid + .{ .encoding = .{ .vbr = 4 } }, // newabbrevlen + .align_32_bits, + .block_len, + }, + }); + assert(state.abbrevs.abbrevs.items.len == @intFromEnum(Abbrev.Builtin.define_abbrev)); + try state.abbrevs.addAbbrevAssumeCapacity(bc.allocator, .{ + .operands = &.{ + .{ .literal = Abbrev.Builtin.define_abbrev.toRecordId() }, + .{ .encoding = .{ .array = 5 } }, // numabbrevops + .abbrev_op, + }, + }); + assert(state.abbrevs.abbrevs.items.len == @intFromEnum(Abbrev.Builtin.unabbrev_record)); + try state.abbrevs.addAbbrevAssumeCapacity(bc.allocator, .{ + .operands = &.{ + .{ .encoding = .{ .vbr = 6 } }, // code + .{ .encoding = .{ .array = 6 } }, // numops + .{ .encoding = .{ .vbr = 6 } }, // ops + }, + }); + assert(state.abbrevs.abbrevs.items.len == @typeInfo(Abbrev.Builtin).Enum.fields.len); + for (abbrevs) |abbrev| try state.abbrevs.addAbbrevAssumeCapacity(bc.allocator, abbrev); +} + +fn endBlock(bc: *BitcodeReader) !void { + if (bc.stack.items.len == 0) return error.InvalidEndBlock; + bc.stack.items[bc.stack.items.len - 1].deinit(bc.allocator); + bc.stack.items.len -= 1; +} + +fn parseBlockInfoBlock(bc: *BitcodeReader) !void { + var block_id: ?u32 = null; + while (true) { + const record = (try bc.nextRecord()) orelse return error.EndOfStream; + switch (record.id) { + Abbrev.Builtin.end_block.toRecordId() => break, + Abbrev.Builtin.define_abbrev.toRecordId() => { + const gop = try bc.block_info.getOrPut(bc.allocator, block_id orelse + return error.UnspecifiedBlockId); + if (!gop.found_existing) gop.value_ptr.* = Block.Info.default; + try gop.value_ptr.abbrevs.addOwnedAbbrev( + bc.allocator, + try record.toOwnedAbbrev(bc.allocator), + ); + }, + Block.Info.set_bid => block_id = std.math.cast(u32, record.operands[0]) orelse + return error.Overflow, + Block.Info.block_name => if (bc.keep_names) { + const gop = try bc.block_info.getOrPut(bc.allocator, block_id orelse + return error.UnspecifiedBlockId); + if (!gop.found_existing) gop.value_ptr.* = Block.Info.default; + const name = try bc.allocator.alloc(u8, record.operands.len); + errdefer bc.allocator.free(name); + for (name, record.operands) |*byte, operand| + byte.* = std.math.cast(u8, operand) orelse return error.InvalidName; + gop.value_ptr.block_name = name; + }, + Block.Info.set_record_name => if (bc.keep_names) { + const gop = try bc.block_info.getOrPut(bc.allocator, block_id orelse + return error.UnspecifiedBlockId); + if (!gop.found_existing) gop.value_ptr.* = Block.Info.default; + const name = try bc.allocator.alloc(u8, record.operands.len - 1); + errdefer bc.allocator.free(name); + for (name, record.operands[1..]) |*byte, operand| + byte.* = std.math.cast(u8, operand) orelse return error.InvalidName; + try gop.value_ptr.record_names.put( + bc.allocator, + std.math.cast(u32, record.operands[0]) orelse return error.Overflow, + name, + ); + }, + else => return error.UnsupportedBlockInfoRecord, + } + } +} + +fn align32Bits(bc: *BitcodeReader) void { + bc.bit_offset = 0; +} + +fn read32Bits(bc: *BitcodeReader) !u32 { + assert(bc.bit_offset == 0); + return bc.reader.readInt(u32, .little); +} + +fn readBytes(bc: *BitcodeReader, bytes: []u8) !void { + assert(bc.bit_offset == 0); + try bc.reader.readNoEof(bytes); + + const trailing_bytes = bytes.len % 4; + if (trailing_bytes > 0) { + var bit_buffer = [1]u8{0} ** 4; + try bc.reader.readNoEof(bit_buffer[trailing_bytes..]); + bc.bit_buffer = std.mem.readInt(u32, &bit_buffer, .little); + bc.bit_offset = @intCast(trailing_bytes * 8); + } +} + +fn readFixed(bc: *BitcodeReader, comptime T: type, bits: u7) !T { + var result: T = 0; + var shift: std.math.Log2IntCeil(T) = 0; + var remaining = bits; + while (remaining > 0) { + if (bc.bit_offset == 0) bc.bit_buffer = try bc.read32Bits(); + const chunk_len = @min(@as(u6, 32) - bc.bit_offset, remaining); + const chunk_mask = @as(u32, std.math.maxInt(u32)) >> @intCast(32 - chunk_len); + result |= @as(T, @intCast(bc.bit_buffer >> bc.bit_offset & chunk_mask)) << @intCast(shift); + shift += @intCast(chunk_len); + remaining -= chunk_len; + bc.bit_offset = @truncate(bc.bit_offset + chunk_len); + } + return result; +} + +fn readVbr(bc: *BitcodeReader, comptime T: type, bits: u7) !T { + const chunk_bits: u6 = @intCast(bits - 1); + const chunk_msb = @as(u64, 1) << chunk_bits; + + var result: u64 = 0; + var shift: u6 = 0; + while (true) { + var chunk = try bc.readFixed(u64, bits); + result |= (chunk & (chunk_msb - 1)) << shift; + if (chunk & chunk_msb == 0) break; + shift += chunk_bits; + } + return @intCast(result); +} + +fn readChar6(bc: *BitcodeReader) !u8 { + return switch (try bc.readFixed(u6, 6)) { + 0...25 => |c| @as(u8, c - 0) + 'a', + 26...51 => |c| @as(u8, c - 26) + 'A', + 52...61 => |c| @as(u8, c - 52) + '0', + 62 => '.', + 63 => '_', + }; +} + +const State = struct { + block_id: ?u32, + abbrev_id_width: u6, + abbrevs: Abbrev.Store, + + fn deinit(state: *State, allocator: std.mem.Allocator) void { + state.abbrevs.deinit(allocator); + state.* = undefined; + } +}; + +const Abbrev = struct { + operands: []const Operand, + + const Builtin = enum(u2) { + end_block, + enter_subblock, + define_abbrev, + unabbrev_record, + + const first_record_id: u32 = std.math.maxInt(u32) - @typeInfo(Builtin).Enum.fields.len + 1; + fn toRecordId(builtin: Builtin) u32 { + return first_record_id + @intFromEnum(builtin); + } + }; + + const Operand = union(enum) { + literal: u64, + encoding: union(Encoding) { + fixed: u7, + vbr: u6, + array: u3, + char6, + blob: u3, + }, + align_32_bits, + block_len, + abbrev_op, + + const literal = std.math.maxInt(u64); + const Encoding = enum(u3) { + fixed = 1, + vbr = 2, + array = 3, + char6 = 4, + blob = 5, + _, + }; + }; + + const Store = struct { + abbrevs: std.ArrayListUnmanaged(Abbrev), + + fn deinit(store: *Store, allocator: std.mem.Allocator) void { + for (store.abbrevs.items) |abbrev| allocator.free(abbrev.operands); + store.abbrevs.deinit(allocator); + store.* = undefined; + } + + fn addAbbrev(store: *Store, allocator: std.mem.Allocator, abbrev: Abbrev) !void { + try store.ensureUnusedCapacity(allocator, 1); + store.addAbbrevAssumeCapacity(abbrev); + } + + fn addAbbrevAssumeCapacity(store: *Store, allocator: std.mem.Allocator, abbrev: Abbrev) !void { + store.abbrevs.appendAssumeCapacity(.{ + .operands = try allocator.dupe(Abbrev.Operand, abbrev.operands), + }); + } + + fn addOwnedAbbrev(store: *Store, allocator: std.mem.Allocator, abbrev: Abbrev) !void { + try store.abbrevs.ensureUnusedCapacity(allocator, 1); + store.addOwnedAbbrevAssumeCapacity(abbrev); + } + + fn addOwnedAbbrevAssumeCapacity(store: *Store, abbrev: Abbrev) void { + store.abbrevs.appendAssumeCapacity(abbrev); + } + }; +}; + +const assert = std.debug.assert; +const std = @import("std"); + +const BitcodeReader = @This(); |
