feature. See also
. The project being documented here (as the example) is the Zig library itself.
Pdb.BinaryAnnotation
pub const BinaryAnnotation = union(enum)
File
Code
pub const BinaryAnnotation = union(enum) {
code_offset: u32,
change_code_offset_base: u32,
change_code_offset: u32,
change_code_length: u32,
change_file: u32,
change_line_offset: i32,
change_line_end_delta: u32,
change_range_kind: RangeKind,
change_column_start: u32,
change_column_end_delta: i32,
change_code_offset_and_line_offset: struct { code_delta: u32, line_delta: i32 },
change_code_length_and_code_offset: struct { length: u32, delta: u32 },
change_column_end: u32,
pub const RangeKind = enum(u32) { expression = 0, statement = 1 };
pub const RangeIterator = struct {
annotations: Iterator,
curr: PartialRange,
prev: ?PartialRange,
const PartialRange = struct {
line_offset: i32,
file_id: ?u32,
code_offset: u32,
code_length: ?u32,
fn resolve(self: PartialRange, next_code_offset: ?u32) ?Range {
return .{
.line_offset = self.line_offset,
.file_id = self.file_id,
.code_offset = self.code_offset,
.code_length = b: {
if (self.code_length) |l| break :b l;
const end = next_code_offset orelse return null;
break :b end - self.code_offset;
},
};
}
};
pub fn init(annotations: Iterator) RangeIterator {
return .{
.annotations = annotations,
.curr = .{
.line_offset = 0,
.file_id = null,
.code_offset = 0,
.code_length = null,
},
.prev = null,
};
}
pub const Range = struct {
line_offset: i32,
file_id: ?u32,
code_offset: u32,
code_length: u32,
pub fn contains(self: Range, offset_in_func: usize) bool {
return self.code_offset <= offset_in_func and
offset_in_func < self.code_offset + self.code_length;
}
};
pub fn next(self: *RangeIterator) error{InvalidDebugInfo}!?Range {
while (try self.annotations.next()) |annotation| {
switch (annotation) {
.change_code_offset => |delta| {
self.curr.code_offset += delta;
},
.change_code_length => |length| {
if (self.prev) |*prev| prev.code_length = prev.code_length orelse length;
self.curr.code_offset += length;
},
// so this logic is untested.
.change_file => |file_id| {
self.curr.file_id = file_id;
},
// may as well handle it in case they emit it in the future
.change_code_length_and_code_offset => |info| {
self.curr.code_length = info.length;
self.curr.code_offset += info.delta;
},
.change_line_offset => |delta| {
self.curr.line_offset += delta;
},
.change_code_offset_and_line_offset => |info| {
self.curr.code_offset += info.code_delta;
self.curr.line_offset += info.line_delta;
},
// without a test case. Safe to ignore since we don't use this info right now.
.change_line_end_delta,
.change_column_start,
.change_column_end_delta,
.change_column_end,
=> {},
// these opcodes should be interpreted, so we make no attempt to handle them.
.code_offset,
.change_code_offset_base,
.change_range_kind,
=> {
self.annotations = .empty;
self.prev = null;
return null;
},
}
// its length if necessary.
switch (annotation) {
.change_code_offset,
.change_code_offset_and_line_offset,
.change_code_length_and_code_offset,
=> {},
else => continue,
}
defer self.prev = self.curr;
const prev = self.prev orelse continue;
return prev.resolve(self.curr.code_offset);
}
// with a known length, return it.
const prev = self.prev orelse return null;
defer self.prev = null;
return prev.resolve(null);
}
};
pub const Iterator = struct {
reader: Io.Reader,
pub const empty: Iterator = .{ .reader = .ending_instance };
pub fn next(self: *Iterator) error{InvalidDebugInfo}!?BinaryAnnotation {
return take(&self.reader) catch |err| switch (err) {
error.ReadFailed => return error.InvalidDebugInfo,
error.EndOfStream => return null,
};
}
};
pub fn take(reader: *Io.Reader) Io.Reader.Error!BinaryAnnotation {
const op = std.enums.fromInt(
pdb.BinaryAnnotationOpcode,
try takePackedU32(reader),
) orelse return error.ReadFailed;
switch (op) {
// whether padding is allowed internally or only after all instructions are complete.
// Empirically, the latter appears to be the case, at least with the output from LLVM
// that I've tested.
.invalid => return error.EndOfStream,
.code_offset => return .{
.code_offset = try expect(takePackedU32(reader)),
},
.change_code_offset_base => return .{
.change_code_offset_base = try expect(takePackedU32(reader)),
},
.change_code_offset => return .{
.change_code_offset = try expect(takePackedU32(reader)),
},
.change_code_length => return .{
.change_code_length = try expect(takePackedU32(reader)),
},
.change_file => return .{
.change_file = try expect(takePackedU32(reader)),
},
.change_line_offset => return .{
.change_line_offset = try expect(takePackedI32(reader)),
},
.change_line_end_delta => return .{
.change_line_end_delta = try expect(takePackedU32(reader)),
},
.change_range_kind => return .{
.change_range_kind = std.enums.fromInt(
RangeKind,
try expect(takePackedU32(reader)),
) orelse return error.ReadFailed,
},
.change_column_start => return .{
.change_column_start = try expect(takePackedU32(reader)),
},
.change_column_end_delta => return .{
.change_column_end_delta = try expect(takePackedI32(reader)),
},
.change_code_offset_and_line_offset => {
const EncodedArgs = packed struct(u32) {
code_delta: u4,
encoded_line_delta: u28,
};
const args: EncodedArgs = @bitCast(try expect(takePackedU32(reader)));
return .{
.change_code_offset_and_line_offset = .{
.code_delta = args.code_delta,
.line_delta = decodeI32(args.encoded_line_delta),
},
};
},
.change_code_length_and_code_offset => return .{
.change_code_length_and_code_offset = .{
.length = try expect(takePackedU32(reader)),
.delta = try expect(takePackedU32(reader)),
},
},
.change_column_end => return .{
.change_column_end = try expect(takePackedU32(reader)),
},
}
}
// https://github.com/microsoft/microsoft-pdb/blob/805655a28bd8198004be2ac27e6e0290121a5e89/include/cvinfo.h#L4942
pub fn takePackedU32(reader: *Io.Reader) Io.Reader.Error!u32 {
const b0: u32 = try reader.takeByte();
if (b0 & 0x80 == 0x00) return b0;
const b1: u32 = try reader.takeByte();
if (b0 & 0xC0 == 0x80) return ((b0 & 0x3F) << 8) | b1;
const b2: u32 = try reader.takeByte();
const b3: u32 = try reader.takeByte();
if (b0 & 0xE0 == 0xC0) return ((b0 & 0x1f) << 24) | (b1 << 16) | (b2 << 8) | b3;
return error.ReadFailed;
}
pub fn takePackedI32(reader: *Io.Reader) Io.Reader.Error!i32 {
return decodeI32(try takePackedU32(reader));
}
pub fn decodeI32(u: u32) i32 {
const i: i32 = @bitCast(u);
if (i & 1 != 0) {
return -(i >> 1);
} else {
return i >> 1;
}
}
fn expect(value: anytype) error{ReadFailed}!@typeInfo(@TypeOf(value)).error_union.payload {
comptime assert(@typeInfo(@TypeOf(value)).error_union.error_set == Io.Reader.Error);
return value catch error.ReadFailed;
}
}