Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

BinaryAnnotation

Pdb.BinaryAnnotation
pub const BinaryAnnotation = union(enum)

File

lib/std/debug/Pdb.zig:305

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 };

    /// A virtual machine that processed binary annotations.
    pub const RangeIterator = struct {
        annotations: Iterator,
        curr: PartialRange,
        /// The previous range is tracked as the code length is sometimes implied by the subsequent
        /// range.
        prev: ?PartialRange,

        const PartialRange = struct {
            line_offset: i32,
            file_id: ?u32,
            code_offset: u32,
            code_length: ?u32,

            /// Resolves a partial range to a range with a definite length, or returns null if this
            /// is not possible.
            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;
                    },
                    // LLVM has code to emit these, but I wasn't able to figure out how trigger it
                    // so this logic is untested.
                    .change_file => |file_id| {
                        self.curr.file_id = file_id;
                    },
                    // LLVM never emits this opcode, but it's clear enough how to interpret it so we
                    // 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;
                    },

                    // Not emitted by LLVM at the time of writing, and we don't want to add support
                    // 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,
                    => {},

                    // Not emitted by LLVM at the time of writing. Various sources conflict on how
                    // 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;
                    },
                }

                // If we have a new code offset, return the previous range if it exists, resolving
                // 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);
            }

            // If we've processed all the binary operations but still have a previous range leftover
            // 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) {
            // Microsoft's docs say that invalid is used as padding, though it is left ambiguous
            // 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)),
            },
        }
    }

    // Adapted from:
    // 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;
    }
}