diff options
Diffstat (limited to 'lib/std')
100 files changed, 16058 insertions, 12429 deletions
diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 2644b79dcd..5a5bce5127 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -2195,7 +2195,7 @@ fn dependencyInner( sub_builder.runBuild(bz) catch @panic("unhandled error"); if (sub_builder.validateUserInputDidItFail()) { - std.debug.dumpCurrentStackTrace(@returnAddress()); + std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() }); } } @@ -2524,7 +2524,10 @@ pub const LazyPath = union(enum) { .up = gen.up, .sub_path = dupePathInner(allocator, gen.sub_path), } }, - .dependency => |dep| .{ .dependency = dep }, + .dependency => |dep| .{ .dependency = .{ + .dependency = dep.dependency, + .sub_path = dupePathInner(allocator, dep.sub_path), + } }, }; } }; diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 3002db628f..6e7e9c4702 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -60,7 +60,7 @@ test_results: TestResults, /// The return address associated with creation of this step that can be useful /// to print along with debugging messages. -debug_stack_trace: []usize, +debug_stack_trace: std.builtin.StackTrace, pub const TestResults = struct { fail_count: u32 = 0, @@ -220,16 +220,9 @@ pub fn init(options: StepOptions) Step { .state = .precheck_unstarted, .max_rss = options.max_rss, .debug_stack_trace = blk: { - if (!std.debug.sys_can_stack_trace) break :blk &.{}; - const addresses = arena.alloc(usize, options.owner.debug_stack_frames_count) catch @panic("OOM"); - @memset(addresses, 0); + const addr_buf = arena.alloc(usize, options.owner.debug_stack_frames_count) catch @panic("OOM"); const first_ret_addr = options.first_ret_addr orelse @returnAddress(); - var stack_trace = std.builtin.StackTrace{ - .instruction_addresses = addresses, - .index = 0, - }; - std.debug.captureStackTrace(first_ret_addr, &stack_trace); - break :blk addresses; + break :blk std.debug.captureCurrentStackTrace(.{ .first_address = first_ret_addr }, addr_buf); }, .result_error_msgs = .{}, .result_error_bundle = std.zig.ErrorBundle.empty, @@ -282,18 +275,6 @@ pub fn dependOn(step: *Step, other: *Step) void { step.dependencies.append(other) catch @panic("OOM"); } -pub fn getStackTrace(s: *Step) ?std.builtin.StackTrace { - var len: usize = 0; - while (len < s.debug_stack_trace.len and s.debug_stack_trace[len] != 0) { - len += 1; - } - - return if (len == 0) null else .{ - .instruction_addresses = s.debug_stack_trace, - .index = len, - }; -} - fn makeNoOp(step: *Step, options: MakeOptions) anyerror!void { _ = options; @@ -315,18 +296,9 @@ pub fn cast(step: *Step, comptime T: type) ?*T { /// For debugging purposes, prints identifying information about this Step. pub fn dump(step: *Step, w: *std.Io.Writer, tty_config: std.Io.tty.Config) void { - const debug_info = std.debug.getSelfDebugInfo() catch |err| { - w.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{ - @errorName(err), - }) catch {}; - return; - }; - if (step.getStackTrace()) |stack_trace| { + if (step.debug_stack_trace.instruction_addresses.len > 0) { w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {}; - std.debug.writeStackTrace(stack_trace, w, debug_info, tty_config) catch |err| { - w.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch {}; - return; - }; + std.debug.writeStackTrace(&step.debug_stack_trace, w, tty_config) catch {}; } else { const field = "debug_stack_frames_count"; comptime assert(@hasField(Build, field)); @@ -552,22 +524,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. } }, .error_bundle => { - const EbHdr = std.zig.Server.Message.ErrorBundle; - const eb_hdr = @as(*align(1) const EbHdr, @ptrCast(body)); - const extra_bytes = - body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len]; - const string_bytes = - body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len]; - // TODO: use @ptrCast when the compiler supports it - const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes); - { - s.result_error_bundle = .{ .string_bytes = &.{}, .extra = &.{} }; - errdefer s.result_error_bundle.deinit(gpa); - s.result_error_bundle.string_bytes = try gpa.dupe(u8, string_bytes); - const extra = try gpa.alloc(u32, unaligned_extra.len); - @memcpy(extra, unaligned_extra); - s.result_error_bundle.extra = extra; - } + s.result_error_bundle = try std.zig.Server.allocErrorBundle(gpa, body); // This message indicates the end of the update. if (watch) break :poll; }, diff --git a/lib/std/Build/Step/CheckObject.zig b/lib/std/Build/Step/CheckObject.zig index 2b5994cc34..56be318a84 100644 --- a/lib/std/Build/Step/CheckObject.zig +++ b/lib/std/Build/Step/CheckObject.zig @@ -1097,16 +1097,10 @@ const MachODumper = struct { for (ctx.symtab.items) |sym| { const sym_name = ctx.getString(sym.n_strx); - if (sym.stab()) { - const tt = switch (sym.n_type) { - macho.N_SO => "SO", - macho.N_OSO => "OSO", - macho.N_BNSYM => "BNSYM", - macho.N_ENSYM => "ENSYM", - macho.N_FUN => "FUN", - macho.N_GSYM => "GSYM", - macho.N_STSYM => "STSYM", - else => "UNKNOWN STAB", + if (sym.n_type.bits.is_stab != 0) { + const tt = switch (sym.n_type.stab) { + _ => "UNKNOWN STAB", + else => @tagName(sym.n_type.stab), }; try writer.print("{x}", .{sym.n_value}); if (sym.n_sect > 0) { @@ -1114,27 +1108,27 @@ const MachODumper = struct { try writer.print(" ({s},{s})", .{ sect.segName(), sect.sectName() }); } try writer.print(" {s} (stab) {s}\n", .{ tt, sym_name }); - } else if (sym.sect()) { + } else if (sym.n_type.bits.type == .sect) { const sect = ctx.sections.items[sym.n_sect - 1]; try writer.print("{x} ({s},{s})", .{ sym.n_value, sect.segName(), sect.sectName(), }); - if (sym.n_desc & macho.REFERENCED_DYNAMICALLY != 0) try writer.writeAll(" [referenced dynamically]"); - if (sym.weakDef()) try writer.writeAll(" weak"); - if (sym.weakRef()) try writer.writeAll(" weakref"); - if (sym.ext()) { - if (sym.pext()) try writer.writeAll(" private"); + if (sym.n_desc.referenced_dynamically) try writer.writeAll(" [referenced dynamically]"); + if (sym.n_desc.weak_def_or_ref_to_weak) try writer.writeAll(" weak"); + if (sym.n_desc.weak_ref) try writer.writeAll(" weakref"); + if (sym.n_type.bits.ext) { + if (sym.n_type.bits.pext) try writer.writeAll(" private"); try writer.writeAll(" external"); - } else if (sym.pext()) try writer.writeAll(" (was private external)"); + } else if (sym.n_type.bits.pext) try writer.writeAll(" (was private external)"); try writer.print(" {s}\n", .{sym_name}); } else if (sym.tentative()) { - const alignment = (sym.n_desc >> 8) & 0x0F; + const alignment = (@as(u16, @bitCast(sym.n_desc)) >> 8) & 0x0F; try writer.print(" 0x{x:0>16} (common) (alignment 2^{d})", .{ sym.n_value, alignment }); - if (sym.ext()) try writer.writeAll(" external"); + if (sym.n_type.bits.ext) try writer.writeAll(" external"); try writer.print(" {s}\n", .{sym_name}); - } else if (sym.undf()) { + } else if (sym.n_type.bits.type == .undf) { const ordinal = @divFloor(@as(i16, @bitCast(sym.n_desc)), macho.N_SYMBOL_RESOLVER); const import_name = blk: { if (ordinal <= 0) { @@ -1153,8 +1147,8 @@ const MachODumper = struct { break :blk basename[0..ext]; }; try writer.writeAll("(undefined)"); - if (sym.weakRef()) try writer.writeAll(" weakref"); - if (sym.ext()) try writer.writeAll(" external"); + if (sym.n_desc.weak_ref) try writer.writeAll(" weakref"); + if (sym.n_type.bits.ext) try writer.writeAll(" external"); try writer.print(" {s} (from {s})\n", .{ sym_name, import_name, diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 8f91a8580d..f05d8490d3 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -595,19 +595,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim } }, .error_bundle => { - const EbHdr = std.zig.Server.Message.ErrorBundle; - const eb_hdr = @as(*align(1) const EbHdr, @ptrCast(body)); - const extra_bytes = - body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len]; - const string_bytes = - body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len]; - const unaligned_extra: []align(1) const u32 = @ptrCast(extra_bytes); - const extra_array = try arena.alloc(u32, unaligned_extra.len); - @memcpy(extra_array, unaligned_extra); - result_error_bundle = .{ - .string_bytes = try arena.dupe(u8, string_bytes), - .extra = extra_array, - }; + result_error_bundle = try std.zig.Server.allocErrorBundle(arena, body); }, .emit_digest => { const EmitDigest = std.zig.Server.Message.EmitDigest; diff --git a/lib/std/Io/Reader.zig b/lib/std/Io/Reader.zig index 6ed0230317..e2344e959e 100644 --- a/lib/std/Io/Reader.zig +++ b/lib/std/Io/Reader.zig @@ -481,7 +481,6 @@ pub fn readVecAll(r: *Reader, data: [][]u8) Error!void { /// is returned instead. /// /// See also: -/// * `peek` /// * `toss` pub fn peek(r: *Reader, n: usize) Error![]u8 { try r.fill(n); @@ -732,7 +731,7 @@ pub const DelimiterError = error{ }; /// Returns a slice of the next bytes of buffered data from the stream until -/// `sentinel` is found, advancing the seek position. +/// `sentinel` is found, advancing the seek position past the sentinel. /// /// Returned slice has a sentinel. /// @@ -765,7 +764,7 @@ pub fn peekSentinel(r: *Reader, comptime sentinel: u8) DelimiterError![:sentinel } /// Returns a slice of the next bytes of buffered data from the stream until -/// `delimiter` is found, advancing the seek position. +/// `delimiter` is found, advancing the seek position past the delimiter. /// /// Returned slice includes the delimiter as the last byte. /// @@ -793,31 +792,42 @@ pub fn takeDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 { /// * `peekDelimiterExclusive` /// * `takeDelimiterInclusive` pub fn peekDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 { - const buffer = r.buffer[0..r.end]; - const seek = r.seek; - if (std.mem.indexOfScalarPos(u8, buffer, seek, delimiter)) |end| { - @branchHint(.likely); - return buffer[seek .. end + 1]; + { + const contents = r.buffer[0..r.end]; + const seek = r.seek; + if (std.mem.findScalarPos(u8, contents, seek, delimiter)) |end| { + @branchHint(.likely); + return contents[seek .. end + 1]; + } } - // TODO take a parameter for max search length rather than relying on buffer capacity - try rebase(r, r.buffer.len); - while (r.buffer.len - r.end != 0) { - const end_cap = r.buffer[r.end..]; - var writer: Writer = .fixed(end_cap); - const n = r.vtable.stream(r, &writer, .limited(end_cap.len)) catch |err| switch (err) { - error.WriteFailed => unreachable, - else => |e| return e, - }; - r.end += n; - if (std.mem.indexOfScalarPos(u8, end_cap[0..n], 0, delimiter)) |end| { - return r.buffer[0 .. r.end - n + end + 1]; + while (true) { + const content_len = r.end - r.seek; + if (r.buffer.len - content_len == 0) break; + try fillMore(r); + const seek = r.seek; + const contents = r.buffer[0..r.end]; + if (std.mem.findScalarPos(u8, contents, seek + content_len, delimiter)) |end| { + return contents[seek .. end + 1]; } } - return error.StreamTooLong; + // It might or might not be end of stream. There is no more buffer space + // left to disambiguate. If `StreamTooLong` was added to `RebaseError` then + // this logic could be replaced by removing the exit condition from the + // above while loop. That error code would represent when `buffer` capacity + // is too small for an operation, replacing the current use of asserts. + var failing_writer = Writer.failing; + while (r.vtable.stream(r, &failing_writer, .limited(1))) |n| { + assert(n == 0); + } else |err| switch (err) { + error.WriteFailed => return error.StreamTooLong, + error.ReadFailed => |e| return e, + error.EndOfStream => |e| return e, + } } /// Returns a slice of the next bytes of buffered data from the stream until -/// `delimiter` is found, advancing the seek position up to the delimiter. +/// `delimiter` is found, advancing the seek position up to (but not past) +/// the delimiter. /// /// Returned slice excludes the delimiter. End-of-stream is treated equivalent /// to a delimiter, unless it would result in a length 0 return value, in which @@ -831,20 +841,13 @@ pub fn peekDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 { /// Invalidates previously returned values from `peek`. /// /// See also: +/// * `takeDelimiter` /// * `takeDelimiterInclusive` /// * `peekDelimiterExclusive` pub fn takeDelimiterExclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 { - const result = r.peekDelimiterInclusive(delimiter) catch |err| switch (err) { - error.EndOfStream => { - const remaining = r.buffer[r.seek..r.end]; - if (remaining.len == 0) return error.EndOfStream; - r.toss(remaining.len); - return remaining; - }, - else => |e| return e, - }; + const result = try r.peekDelimiterExclusive(delimiter); r.toss(result.len); - return result[0 .. result.len - 1]; + return result; } /// Returns a slice of the next bytes of buffered data from the stream until @@ -865,7 +868,7 @@ pub fn takeDelimiterExclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 { /// * `takeDelimiterInclusive` /// * `takeDelimiterExclusive` pub fn takeDelimiter(r: *Reader, delimiter: u8) error{ ReadFailed, StreamTooLong }!?[]u8 { - const result = r.peekDelimiterInclusive(delimiter) catch |err| switch (err) { + const inclusive = r.peekDelimiterInclusive(delimiter) catch |err| switch (err) { error.EndOfStream => { const remaining = r.buffer[r.seek..r.end]; if (remaining.len == 0) return null; @@ -874,8 +877,8 @@ pub fn takeDelimiter(r: *Reader, delimiter: u8) error{ ReadFailed, StreamTooLong }, else => |e| return e, }; - r.toss(result.len + 1); - return result[0 .. result.len - 1]; + r.toss(inclusive.len); + return inclusive[0 .. inclusive.len - 1]; } /// Returns a slice of the next bytes of buffered data from the stream until @@ -1402,6 +1405,9 @@ test peekSentinel { var r: Reader = .fixed("ab\nc"); try testing.expectEqualStrings("ab", try r.peekSentinel('\n')); try testing.expectEqualStrings("ab", try r.peekSentinel('\n')); + r.toss(3); + try testing.expectError(error.EndOfStream, r.peekSentinel('\n')); + try testing.expectEqualStrings("c", try r.peek(1)); } test takeDelimiterInclusive { @@ -1416,22 +1422,52 @@ test peekDelimiterInclusive { try testing.expectEqualStrings("ab\n", try r.peekDelimiterInclusive('\n')); r.toss(3); try testing.expectError(error.EndOfStream, r.peekDelimiterInclusive('\n')); + try testing.expectEqualStrings("c", try r.peek(1)); } test takeDelimiterExclusive { var r: Reader = .fixed("ab\nc"); + try testing.expectEqualStrings("ab", try r.takeDelimiterExclusive('\n')); + try testing.expectEqualStrings("", try r.takeDelimiterExclusive('\n')); + try testing.expectEqualStrings("", try r.takeDelimiterExclusive('\n')); + try testing.expectEqualStrings("\n", try r.take(1)); + try testing.expectEqualStrings("c", try r.takeDelimiterExclusive('\n')); try testing.expectError(error.EndOfStream, r.takeDelimiterExclusive('\n')); } test peekDelimiterExclusive { var r: Reader = .fixed("ab\nc"); + try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n')); try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n')); - r.toss(3); + r.toss(2); + try testing.expectEqualStrings("", try r.peekDelimiterExclusive('\n')); + try testing.expectEqualStrings("\n", try r.take(1)); + try testing.expectEqualStrings("c", try r.peekDelimiterExclusive('\n')); try testing.expectEqualStrings("c", try r.peekDelimiterExclusive('\n')); + r.toss(1); + try testing.expectError(error.EndOfStream, r.peekDelimiterExclusive('\n')); +} + +test takeDelimiter { + var r: Reader = .fixed("ab\nc\n\nd"); + try testing.expectEqualStrings("ab", (try r.takeDelimiter('\n')).?); + try testing.expectEqualStrings("c", (try r.takeDelimiter('\n')).?); + try testing.expectEqualStrings("", (try r.takeDelimiter('\n')).?); + try testing.expectEqualStrings("d", (try r.takeDelimiter('\n')).?); + try testing.expectEqual(null, try r.takeDelimiter('\n')); + try testing.expectEqual(null, try r.takeDelimiter('\n')); + + r = .fixed("ab\nc\n\nd\n"); // one trailing newline does not affect behavior + try testing.expectEqualStrings("ab", (try r.takeDelimiter('\n')).?); + try testing.expectEqualStrings("c", (try r.takeDelimiter('\n')).?); + try testing.expectEqualStrings("", (try r.takeDelimiter('\n')).?); + try testing.expectEqualStrings("d", (try r.takeDelimiter('\n')).?); + try testing.expectEqual(null, try r.takeDelimiter('\n')); + try testing.expectEqual(null, try r.takeDelimiter('\n')); } test streamDelimiter { @@ -1601,6 +1637,18 @@ test "readSliceShort with smaller buffer than Reader" { try testing.expectEqualStrings(str, &buf); } +test "readSliceShort with indirect reader" { + var r: Reader = .fixed("HelloFren"); + var ri_buf: [3]u8 = undefined; + var ri: std.testing.ReaderIndirect = .init(&r, &ri_buf); + var buf: [5]u8 = undefined; + try testing.expectEqual(5, try ri.interface.readSliceShort(&buf)); + try testing.expectEqualStrings("Hello", buf[0..5]); + try testing.expectEqual(4, try ri.interface.readSliceShort(&buf)); + try testing.expectEqualStrings("Fren", buf[0..4]); + try testing.expectEqual(0, try ri.interface.readSliceShort(&buf)); +} + test readVec { var r: Reader = .fixed(std.ascii.letters); var flat_buffer: [52]u8 = undefined; @@ -1701,6 +1749,26 @@ test "takeDelimiterInclusive when it rebases" { } } +test "takeDelimiterInclusive on an indirect reader when it rebases" { + const written_line = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"; + var buffer: [128]u8 = undefined; + var tr: std.testing.Reader = .init(&buffer, &.{ + .{ .buffer = written_line[0..4] }, + .{ .buffer = written_line[4..] }, + .{ .buffer = written_line }, + .{ .buffer = written_line }, + .{ .buffer = written_line }, + .{ .buffer = written_line }, + .{ .buffer = written_line }, + }); + var indirect_buffer: [128]u8 = undefined; + var tri: std.testing.ReaderIndirect = .init(&tr.interface, &indirect_buffer); + const r = &tri.interface; + for (0..6) |_| { + try std.testing.expectEqualStrings(written_line, try r.takeDelimiterInclusive('\n')); + } +} + test "takeStruct and peekStruct packed" { var r: Reader = .fixed(&.{ 0b11110000, 0b00110011 }); const S = packed struct(u16) { a: u2, b: u6, c: u7, d: u1 }; diff --git a/lib/std/Io/Reader/Limited.zig b/lib/std/Io/Reader/Limited.zig index e0d04bb531..d983133307 100644 --- a/lib/std/Io/Reader/Limited.zig +++ b/lib/std/Io/Reader/Limited.zig @@ -27,6 +27,7 @@ pub fn init(reader: *Reader, limit: Limit, buffer: []u8) Limited { fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize { const l: *Limited = @fieldParentPtr("interface", r); + if (l.remaining == .nothing) return error.EndOfStream; const combined_limit = limit.min(l.remaining); const n = try l.unlimited.stream(w, combined_limit); l.remaining = l.remaining.subtract(n).?; @@ -51,8 +52,51 @@ test stream { fn discard(r: *Reader, limit: Limit) Reader.Error!usize { const l: *Limited = @fieldParentPtr("interface", r); + if (l.remaining == .nothing) return error.EndOfStream; const combined_limit = limit.min(l.remaining); const n = try l.unlimited.discard(combined_limit); l.remaining = l.remaining.subtract(n).?; return n; } + +test "end of stream, read, hit limit exactly" { + var f: Reader = .fixed("i'm dying"); + var l = f.limited(.limited(4), &.{}); + const r = &l.interface; + + var buf: [2]u8 = undefined; + try r.readSliceAll(&buf); + try r.readSliceAll(&buf); + try std.testing.expectError(error.EndOfStream, l.interface.readSliceAll(&buf)); +} + +test "end of stream, read, hit limit after partial read" { + var f: Reader = .fixed("i'm dying"); + var l = f.limited(.limited(5), &.{}); + const r = &l.interface; + + var buf: [2]u8 = undefined; + try r.readSliceAll(&buf); + try r.readSliceAll(&buf); + try std.testing.expectError(error.EndOfStream, l.interface.readSliceAll(&buf)); +} + +test "end of stream, discard, hit limit exactly" { + var f: Reader = .fixed("i'm dying"); + var l = f.limited(.limited(4), &.{}); + const r = &l.interface; + + try r.discardAll(2); + try r.discardAll(2); + try std.testing.expectError(error.EndOfStream, l.interface.discardAll(2)); +} + +test "end of stream, discard, hit limit after partial read" { + var f: Reader = .fixed("i'm dying"); + var l = f.limited(.limited(5), &.{}); + const r = &l.interface; + + try r.discardAll(2); + try r.discardAll(2); + try std.testing.expectError(error.EndOfStream, l.interface.discardAll(2)); +} diff --git a/lib/std/Io/Writer.zig b/lib/std/Io/Writer.zig index 77383ae278..7ad466d5cb 100644 --- a/lib/std/Io/Writer.zig +++ b/lib/std/Io/Writer.zig @@ -923,10 +923,12 @@ pub fn sendFileHeader( return n; } -/// Asserts nonzero buffer capacity. +/// Asserts nonzero buffer capacity and nonzero `limit`. pub fn sendFileReading(w: *Writer, file_reader: *File.Reader, limit: Limit) FileReadingError!usize { + assert(limit != .nothing); const dest = limit.slice(try w.writableSliceGreedy(1)); - const n = try file_reader.read(dest); + const n = try file_reader.interface.readSliceShort(dest); + if (n == 0) return error.EndOfStream; w.advance(n); return n; } @@ -2778,7 +2780,8 @@ pub const Allocating = struct { if (additional == 0) return error.EndOfStream; a.ensureUnusedCapacity(limit.minInt64(additional)) catch return error.WriteFailed; const dest = limit.slice(a.writer.buffer[a.writer.end..]); - const n = try file_reader.read(dest); + const n = try file_reader.interface.readSliceShort(dest); + if (n == 0) return error.EndOfStream; a.writer.end += n; return n; } @@ -2849,18 +2852,40 @@ test "allocating sendFile" { const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); defer file.close(); - var r_buffer: [256]u8 = undefined; + var r_buffer: [2]u8 = undefined; var file_writer: std.fs.File.Writer = .init(file, &r_buffer); - try file_writer.interface.writeByte('h'); + try file_writer.interface.writeAll("abcd"); try file_writer.interface.flush(); var file_reader = file_writer.moveToReader(); try file_reader.seekTo(0); + try file_reader.interface.fill(2); var allocating: Writer.Allocating = .init(testing.allocator); defer allocating.deinit(); + try allocating.ensureUnusedCapacity(1); + try testing.expectEqual(4, allocating.writer.sendFileAll(&file_reader, .unlimited)); + try testing.expectEqualStrings("abcd", allocating.writer.buffered()); +} + +test sendFileReading { + var tmp_dir = testing.tmpDir(.{}); + defer tmp_dir.cleanup(); + + const file = try tmp_dir.dir.createFile("input.txt", .{ .read = true }); + defer file.close(); + var r_buffer: [2]u8 = undefined; + var file_writer: std.fs.File.Writer = .init(file, &r_buffer); + try file_writer.interface.writeAll("abcd"); + try file_writer.interface.flush(); - _ = try file_reader.interface.streamRemaining(&allocating.writer); + var file_reader = file_writer.moveToReader(); + try file_reader.seekTo(0); + try file_reader.interface.fill(2); + + var w_buffer: [1]u8 = undefined; + var discarding: Writer.Discarding = .init(&w_buffer); + try testing.expectEqual(4, discarding.writer.sendFileReadingAll(&file_reader, .unlimited)); } test writeStruct { diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 027872de54..3fb9587c82 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -1082,7 +1082,7 @@ pub fn toElfMachine(target: *const Target) std.elf.EM { }; } -pub fn toCoffMachine(target: *const Target) std.coff.MachineType { +pub fn toCoffMachine(target: *const Target) std.coff.IMAGE.FILE.MACHINE { return switch (target.cpu.arch) { .arm => .ARM, .thumb => .ARMNT, @@ -1092,7 +1092,7 @@ pub fn toCoffMachine(target: *const Target) std.coff.MachineType { .riscv32 => .RISCV32, .riscv64 => .RISCV64, .x86 => .I386, - .x86_64 => .X64, + .x86_64 => .AMD64, .amdgcn, .arc, @@ -1912,7 +1912,11 @@ pub const Cpu = struct { .powerpc64le => &powerpc.cpu.ppc64le, .riscv32, .riscv32be => &riscv.cpu.baseline_rv32, .riscv64, .riscv64be => &riscv.cpu.baseline_rv64, - .s390x => &s390x.cpu.arch8, // gcc/clang do not have a generic s390x model. + // gcc/clang do not have a generic s390x model. + .s390x => switch (os.tag) { + .zos => &s390x.cpu.arch10, + else => &s390x.cpu.arch8, + }, .sparc => &sparc.cpu.v9, // glibc does not work with 'plain' v8. .sparc64 => switch (os.tag) { .solaris => &sparc.cpu.ultrasparc3, @@ -2459,9 +2463,9 @@ pub const DynamicLinker = struct { => init("/lib/ld.so.1"), else => none, }, - // TODO: ELFv2 ABI (`/lib64/ld64.so.2`) opt-in support. - .powerpc64 => if (abi == .gnu) init("/lib64/ld64.so.1") else none, - .powerpc64le => if (abi == .gnu) init("/lib64/ld64.so.2") else none, + .powerpc64, + .powerpc64le, + => if (abi == .gnu) init("/lib64/ld64.so.2") else none, .riscv32, .riscv64, diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index d377172f08..6cc377b5bd 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -250,7 +250,7 @@ pub const GetNameError = error{ Unexpected, } || posix.PrctlError || posix.ReadError || std.fs.File.OpenError || std.fmt.BufPrintError; -/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]const u8 { buffer_ptr[max_name_len] = 0; @@ -530,7 +530,7 @@ fn callFn(comptime f: anytype, args: anytype) switch (Impl) { @call(.auto, f, args) catch |err| { std.debug.print("error: {s}\n", .{@errorName(err)}); if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); + std.debug.dumpStackTrace(trace); } }; diff --git a/lib/std/array_hash_map.zig b/lib/std/array_hash_map.zig index 02ae8895aa..2550e0aebe 100644 --- a/lib/std/array_hash_map.zig +++ b/lib/std/array_hash_map.zig @@ -50,7 +50,7 @@ pub fn eqlString(a: []const u8, b: []const u8) bool { } pub fn hashString(s: []const u8) u32 { - return @as(u32, @truncate(std.hash.Wyhash.hash(0, s))); + return @truncate(std.hash.Wyhash.hash(0, s)); } /// Deprecated in favor of `ArrayHashMapWithAllocator` (no code changes needed) @@ -460,10 +460,19 @@ pub fn ArrayHashMapWithAllocator( /// Sorts the entries and then rebuilds the index. /// `sort_ctx` must have this method: /// `fn lessThan(ctx: @TypeOf(ctx), a_index: usize, b_index: usize) bool` + /// Uses a stable sorting algorithm. pub fn sort(self: *Self, sort_ctx: anytype) void { return self.unmanaged.sortContext(sort_ctx, self.ctx); } + /// Sorts the entries and then rebuilds the index. + /// `sort_ctx` must have this method: + /// `fn lessThan(ctx: @TypeOf(ctx), a_index: usize, b_index: usize) bool` + /// Uses an unstable sorting algorithm. + pub fn sortUnstable(self: *Self, sort_ctx: anytype) void { + return self.unmanaged.sortUnstableContext(sort_ctx, self.ctx); + } + /// Shrinks the underlying `Entry` array to `new_len` elements and /// discards any associated index entries. Keeps capacity the same. /// diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index 96a0344442..11202a186e 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -277,14 +277,13 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type /// The empty slot is filled from the end of the list. /// This operation is O(1). /// This may not preserve item order. Use `orderedRemove` if you need to preserve order. - /// Asserts that the list is not empty. /// Asserts that the index is in bounds. pub fn swapRemove(self: *Self, i: usize) T { - if (self.items.len - 1 == i) return self.pop().?; - - const old_item = self.items[i]; - self.items[i] = self.pop().?; - return old_item; + const val = self.items[i]; + self.items[i] = self.items[self.items.len - 1]; + self.items[self.items.len - 1] = undefined; + self.items.len -= 1; + return val; } /// Append the slice of items to the list. Allocates more @@ -522,6 +521,7 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type pub fn pop(self: *Self) ?T { if (self.items.len == 0) return null; const val = self.items[self.items.len - 1]; + self.items[self.items.len - 1] = undefined; self.items.len -= 1; return val; } @@ -544,8 +544,7 @@ pub fn AlignedManaged(comptime T: type, comptime alignment: ?mem.Alignment) type /// Returns the last element from the list. /// Asserts that the list is not empty. pub fn getLast(self: Self) T { - const val = self.items[self.items.len - 1]; - return val; + return self.items[self.items.len - 1]; } /// Returns the last element from the list, or `null` if list is empty. @@ -956,14 +955,13 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// The empty slot is filled from the end of the list. /// Invalidates pointers to last element. /// This operation is O(1). - /// Asserts that the list is not empty. /// Asserts that the index is in bounds. pub fn swapRemove(self: *Self, i: usize) T { - if (self.items.len - 1 == i) return self.pop().?; - - const old_item = self.items[i]; - self.items[i] = self.pop().?; - return old_item; + const val = self.items[i]; + self.items[i] = self.items[self.items.len - 1]; + self.items[self.items.len - 1] = undefined; + self.items.len -= 1; + return val; } /// Append the slice of items to the list. Allocates more @@ -1327,6 +1325,7 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { pub fn pop(self: *Self) ?T { if (self.items.len == 0) return null; const val = self.items[self.items.len - 1]; + self.items[self.items.len - 1] = undefined; self.items.len -= 1; return val; } @@ -1348,8 +1347,7 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { /// Return the last element from the list. /// Asserts that the list is not empty. pub fn getLast(self: Self) T { - const val = self.items[self.items.len - 1]; - return val; + return self.items[self.items.len - 1]; } /// Return the last element from the list, or diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 2504b8fe2f..a3f3e791e6 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -38,20 +38,17 @@ pub const StackTrace = struct { index: usize, instruction_addresses: []usize, - pub fn format(self: StackTrace, writer: *std.Io.Writer) std.Io.Writer.Error!void { + pub fn format(st: *const StackTrace, writer: *std.Io.Writer) std.Io.Writer.Error!void { // TODO: re-evaluate whether to use format() methods at all. // Until then, avoid an error when using GeneralPurposeAllocator with WebAssembly // where it tries to call detectTTYConfig here. if (builtin.os.tag == .freestanding) return; - const debug_info = std.debug.getSelfDebugInfo() catch |err| { - return writer.print("\nUnable to print stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}); - }; - const tty_config = std.Io.tty.detectConfig(std.fs.File.stderr()); + // TODO: why on earth are we using stderr's ttyconfig? + // If we want colored output, we should just make a formatter out of `writeStackTrace`. + const tty_config = std.Io.tty.detectConfig(.stderr()); try writer.writeAll("\n"); - std.debug.writeStackTrace(self, writer, debug_info, tty_config) catch |err| { - try writer.print("Unable to print stack trace: {s}\n", .{@errorName(err)}); - }; + try std.debug.writeStackTrace(st, writer, tty_config); } }; diff --git a/lib/std/c.zig b/lib/std/c.zig index b2b851c43a..248dd2f92f 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -1841,110 +1841,6 @@ pub const PROT = switch (native_os) { else => void, }; -pub const REG = switch (native_os) { - .linux => linux.REG, - .emscripten => emscripten.REG, - .freebsd => switch (builtin.cpu.arch) { - .aarch64 => struct { - pub const FP = 29; - pub const SP = 31; - pub const PC = 32; - }, - .arm => struct { - pub const FP = 11; - pub const SP = 13; - pub const PC = 15; - }, - .x86_64 => struct { - pub const RBP = 12; - pub const RIP = 21; - pub const RSP = 24; - }, - else => struct {}, - }, - .solaris, .illumos => struct { - pub const R15 = 0; - pub const R14 = 1; - pub const R13 = 2; - pub const R12 = 3; - pub const R11 = 4; - pub const R10 = 5; - pub const R9 = 6; - pub const R8 = 7; - pub const RDI = 8; - pub const RSI = 9; - pub const RBP = 10; - pub const RBX = 11; - pub const RDX = 12; - pub const RCX = 13; - pub const RAX = 14; - pub const RIP = 17; - pub const RSP = 20; - }, - .netbsd => switch (builtin.cpu.arch) { - .aarch64, .aarch64_be => struct { - pub const FP = 29; - pub const SP = 31; - pub const PC = 32; - }, - .arm, .armeb => struct { - pub const FP = 11; - pub const SP = 13; - pub const PC = 15; - }, - .x86 => struct { - pub const GS = 0; - pub const FS = 1; - pub const ES = 2; - pub const DS = 3; - pub const EDI = 4; - pub const ESI = 5; - pub const EBP = 6; - pub const ESP = 7; - pub const EBX = 8; - pub const EDX = 9; - pub const ECX = 10; - pub const EAX = 11; - pub const TRAPNO = 12; - pub const ERR = 13; - pub const EIP = 14; - pub const CS = 15; - pub const EFL = 16; - pub const UESP = 17; - pub const SS = 18; - }, - .x86_64 => struct { - pub const RDI = 0; - pub const RSI = 1; - pub const RDX = 2; - pub const RCX = 3; - pub const R8 = 4; - pub const R9 = 5; - pub const R10 = 6; - pub const R11 = 7; - pub const R12 = 8; - pub const R13 = 9; - pub const R14 = 10; - pub const R15 = 11; - pub const RBP = 12; - pub const RBX = 13; - pub const RAX = 14; - pub const GS = 15; - pub const FS = 16; - pub const ES = 17; - pub const DS = 18; - pub const TRAPNO = 19; - pub const ERR = 20; - pub const RIP = 21; - pub const CS = 22; - pub const RFLAGS = 23; - pub const RSP = 24; - pub const SS = 25; - }, - else => struct {}, - }, - else => struct {}, -}; pub const RLIM = switch (native_os) { .linux => linux.RLIM, .emscripten => emscripten.RLIM, @@ -3140,8 +3036,17 @@ pub const SIG = switch (native_os) { pub const UNBLOCK = 2; pub const SETMASK = 3; }, + // https://github.com/SerenityOS/serenity/blob/046c23f567a17758d762a33bdf04bacbfd088f9f/Kernel/API/POSIX/signal.h // https://github.com/SerenityOS/serenity/blob/046c23f567a17758d762a33bdf04bacbfd088f9f/Kernel/API/POSIX/signal_numbers.h .serenity => struct { + pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0); + pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize)); + pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1); + + pub const BLOCK = 1; + pub const UNBLOCK = 2; + pub const SETMASK = 3; + pub const INVAL = 0; pub const HUP = 1; pub const INT = 2; @@ -3346,15 +3251,15 @@ pub const Sigaction = switch (native_os) { }, // https://github.com/SerenityOS/serenity/blob/ec492a1a0819e6239ea44156825c4ee7234ca3db/Kernel/API/POSIX/signal.h#L39-L46 .serenity => extern struct { - pub const handler_fn = *align(1) const fn (c_int) callconv(.c) void; - pub const sigaction_fn = *const fn (c_int, *const siginfo_t, ?*anyopaque) callconv(.c) void; + pub const handler_fn = *align(1) const fn (i32) callconv(.c) void; + pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void; handler: extern union { handler: ?handler_fn, sigaction: ?sigaction_fn, }, mask: sigset_t, - flags: c_int, + flags: c_uint, }, else => void, }; @@ -4544,7 +4449,7 @@ pub const rusage = switch (native_os) { pub const siginfo_t = switch (native_os) { .linux => linux.siginfo_t, .emscripten => emscripten.siginfo_t, - .macos, .ios, .tvos, .watchos, .visionos => extern struct { + .driverkit, .macos, .ios, .tvos, .watchos, .visionos => extern struct { signo: c_int, errno: c_int, code: c_int, @@ -7035,205 +6940,6 @@ pub const timezone = switch (native_os) { else => void, }; -pub const ucontext_t = switch (native_os) { - .linux => linux.ucontext_t, // std.os.linux.ucontext_t is currently glibc-compatible, but it should probably not be. - .emscripten => emscripten.ucontext_t, - .macos, .ios, .tvos, .watchos, .visionos => extern struct { - onstack: c_int, - sigmask: sigset_t, - stack: stack_t, - link: ?*ucontext_t, - mcsize: u64, - mcontext: *mcontext_t, - __mcontext_data: mcontext_t, - }, - .freebsd => extern struct { - sigmask: sigset_t, - mcontext: mcontext_t, - link: ?*ucontext_t, - stack: stack_t, - flags: c_int, - __spare__: [4]c_int, - }, - .solaris, .illumos => extern struct { - flags: u64, - link: ?*ucontext_t, - sigmask: sigset_t, - stack: stack_t, - mcontext: mcontext_t, - brand_data: [3]?*anyopaque, - filler: [2]i64, - }, - .netbsd => extern struct { - flags: u32, - link: ?*ucontext_t, - sigmask: sigset_t, - stack: stack_t, - mcontext: mcontext_t, - __pad: [ - switch (builtin.cpu.arch) { - .x86 => 4, - .mips, .mipsel, .mips64, .mips64el => 14, - .arm, .armeb, .thumb, .thumbeb => 1, - .sparc, .sparc64 => if (@sizeOf(usize) == 4) 43 else 8, - else => 0, - } - ]u32, - }, - .dragonfly => extern struct { - sigmask: sigset_t, - mcontext: mcontext_t, - link: ?*ucontext_t, - stack: stack_t, - cofunc: ?*fn (?*ucontext_t, ?*anyopaque) void, - arg: ?*void, - _spare: [4]c_int, - }, - // https://github.com/SerenityOS/serenity/blob/87eac0e424cff4a1f941fb704b9362a08654c24d/Kernel/API/POSIX/ucontext.h#L19-L24 - .haiku, .serenity => extern struct { - link: ?*ucontext_t, - sigmask: sigset_t, - stack: stack_t, - mcontext: mcontext_t, - }, - .openbsd => openbsd.ucontext_t, - else => void, -}; -pub const mcontext_t = switch (native_os) { - .linux => linux.mcontext_t, - .emscripten => emscripten.mcontext_t, - .macos, .ios, .tvos, .watchos, .visionos => darwin.mcontext_t, - .freebsd => switch (builtin.cpu.arch) { - .x86_64 => extern struct { - onstack: u64, - rdi: u64, - rsi: u64, - rdx: u64, - rcx: u64, - r8: u64, - r9: u64, - rax: u64, - rbx: u64, - rbp: u64, - r10: u64, - r11: u64, - r12: u64, - r13: u64, - r14: u64, - r15: u64, - trapno: u32, - fs: u16, - gs: u16, - addr: u64, - flags: u32, - es: u16, - ds: u16, - err: u64, - rip: u64, - cs: u64, - rflags: u64, - rsp: u64, - ss: u64, - len: u64, - fpformat: u64, - ownedfp: u64, - fpstate: [64]u64 align(16), - fsbase: u64, - gsbase: u64, - xfpustate: u64, - xfpustate_len: u64, - spare: [4]u64, - }, - .aarch64 => extern struct { - gpregs: extern struct { - x: [30]u64, - lr: u64, - sp: u64, - elr: u64, - spsr: u32, - _pad: u32, - }, - fpregs: extern struct { - q: [32]u128, - sr: u32, - cr: u32, - flags: u32, - _pad: u32, - }, - flags: u32, - _pad: u32, - _spare: [8]u64, - }, - else => struct {}, - }, - .solaris, .illumos => extern struct { - gregs: [28]u64, - fpregs: solaris.fpregset_t, - }, - .netbsd => switch (builtin.cpu.arch) { - .aarch64, .aarch64_be => extern struct { - gregs: [35]u64, - fregs: [528]u8 align(16), - spare: [8]u64, - }, - .x86 => extern struct { - gregs: [19]u32, - fpregs: [161]u32, - mc_tlsbase: u32, - }, - .x86_64 => extern struct { - gregs: [26]u64, - mc_tlsbase: u64, - fpregs: [512]u8 align(8), - }, - else => struct {}, - }, - .dragonfly => dragonfly.mcontext_t, - .haiku => haiku.mcontext_t, - .serenity => switch (native_arch) { - // https://github.com/SerenityOS/serenity/blob/200e91cd7f1ec5453799a2720d4dc114a59cc289/Kernel/Arch/aarch64/mcontext.h#L15-L19 - .aarch64 => extern struct { - x: [31]u64, - sp: u64, - pc: u64, - }, - // https://github.com/SerenityOS/serenity/blob/66f8d0f031ef25c409dbb4fecaa454800fecae0f/Kernel/Arch/riscv64/mcontext.h#L15-L18 - .riscv64 => extern struct { - x: [31]u64, - pc: u64, - }, - // https://github.com/SerenityOS/serenity/blob/7b9ea3efdec9f86a1042893e8107d0b23aad8727/Kernel/Arch/x86_64/mcontext.h#L15-L40 - .x86_64 => extern struct { - rax: u64, - rcx: u64, - rdx: u64, - rbx: u64, - rsp: u64, - rbp: u64, - rsi: u64, - rdi: u64, - rip: u64, - r8: u64, - r9: u64, - r10: u64, - r11: u64, - r12: u64, - r13: u64, - r14: u64, - r15: u64, - rflags: u64, - cs: u32, - ss: u32, - ds: u32, - es: u32, - fs: u32, - gs: u32, - }, - else => struct {}, - }, - else => void, -}; - pub const user_desc = switch (native_os) { .linux => linux.user_desc, else => void, @@ -11193,6 +10899,9 @@ pub extern "c" fn dlclose(handle: *anyopaque) c_int; pub extern "c" fn dlsym(handle: ?*anyopaque, symbol: [*:0]const u8) ?*anyopaque; pub extern "c" fn dlerror() ?[*:0]u8; +pub const dladdr = if (native_os.isDarwin()) darwin.dladdr else {}; +pub const dl_info = if (native_os.isDarwin()) darwin.dl_info else {}; + pub extern "c" fn sync() void; pub extern "c" fn syncfs(fd: c_int) c_int; pub extern "c" fn fsync(fd: c_int) c_int; @@ -11238,13 +10947,6 @@ pub const LC = enum(c_int) { pub extern "c" fn setlocale(category: LC, locale: ?[*:0]const u8) ?[*:0]const u8; -pub const getcontext = if (builtin.target.abi.isAndroid() or builtin.target.os.tag == .openbsd or builtin.target.os.tag == .haiku) -{} // libc does not implement getcontext - else if (native_os == .linux and builtin.target.abi.isMusl()) - linux.getcontext - else - private.getcontext; - pub const max_align_t = if (native_abi == .msvc or native_abi == .itanium) f64 else if (native_os.isDarwin()) @@ -11278,7 +10980,6 @@ pub const SETUSTACK = solaris.GETUSTACK; pub const SFD = solaris.SFD; pub const ctid_t = solaris.ctid_t; pub const file_obj = solaris.file_obj; -pub const fpregset_t = solaris.fpregset_t; pub const id_t = solaris.id_t; pub const lif_ifinfo_req = solaris.lif_ifinfo_req; pub const lif_nd_req = solaris.lif_nd_req; @@ -11668,7 +11369,6 @@ const private = struct { extern "c" fn shm_open(name: [*:0]const u8, flag: c_int, mode: mode_t) c_int; extern "c" fn pthread_setname_np(thread: pthread_t, name: [*:0]const u8) c_int; - extern "c" fn getcontext(ucp: *ucontext_t) c_int; extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize; extern "c" fn getentropy(buffer: [*]u8, size: usize) c_int; diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index 2d3376b858..cf7d3127eb 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -348,113 +348,20 @@ pub const VM = struct { pub const exception_type_t = c_int; -pub const mcontext_t = switch (native_arch) { - .aarch64 => extern struct { - es: exception_state, - ss: thread_state, - ns: neon_state, - }, - .x86_64 => extern struct { - es: exception_state, - ss: thread_state, - fs: float_state, - }, - else => @compileError("unsupported arch"), -}; - -pub const exception_state = switch (native_arch) { - .aarch64 => extern struct { - far: u64, // Virtual Fault Address - esr: u32, // Exception syndrome - exception: u32, // Number of arm exception taken - }, - .x86_64 => extern struct { - trapno: u16, - cpu: u16, - err: u32, - faultvaddr: u64, - }, - else => @compileError("unsupported arch"), -}; - -pub const thread_state = switch (native_arch) { - .aarch64 => extern struct { - /// General purpose registers - regs: [29]u64, - /// Frame pointer x29 - fp: u64, - /// Link register x30 - lr: u64, - /// Stack pointer x31 - sp: u64, - /// Program counter - pc: u64, - /// Current program status register - cpsr: u32, - __pad: u32, - }, - .x86_64 => extern struct { - rax: u64, - rbx: u64, - rcx: u64, - rdx: u64, - rdi: u64, - rsi: u64, - rbp: u64, - rsp: u64, - r8: u64, - r9: u64, - r10: u64, - r11: u64, - r12: u64, - r13: u64, - r14: u64, - r15: u64, - rip: u64, - rflags: u64, - cs: u64, - fs: u64, - gs: u64, - }, - else => @compileError("unsupported arch"), -}; - -pub const neon_state = extern struct { - q: [32]u128, - fpsr: u32, - fpcr: u32, -}; - -pub const float_state = extern struct { - reserved: [2]c_int, - fcw: u16, - fsw: u16, - ftw: u8, - rsrv1: u8, - fop: u16, - ip: u32, - cs: u16, - rsrv2: u16, - dp: u32, - ds: u16, - rsrv3: u16, - mxcsr: u32, - mxcsrmask: u32, - stmm: [8]stmm_reg, - xmm: [16]xmm_reg, - rsrv4: [96]u8, - reserved1: c_int, -}; - -pub const stmm_reg = [16]u8; -pub const xmm_reg = [16]u8; - pub extern "c" fn NSVersionOfRunTimeLibrary(library_name: [*:0]const u8) u32; pub extern "c" fn _NSGetExecutablePath(buf: [*:0]u8, bufsize: *u32) c_int; pub extern "c" fn _dyld_image_count() u32; pub extern "c" fn _dyld_get_image_header(image_index: u32) ?*mach_header; pub extern "c" fn _dyld_get_image_vmaddr_slide(image_index: u32) usize; pub extern "c" fn _dyld_get_image_name(image_index: u32) [*:0]const u8; +pub extern "c" fn dladdr(addr: *const anyopaque, info: *dl_info) c_int; + +pub const dl_info = extern struct { + fname: [*:0]const u8, + fbase: *anyopaque, + sname: ?[*:0]const u8, + saddr: ?*anyopaque, +}; pub const COPYFILE = packed struct(u32) { ACL: bool = false, diff --git a/lib/std/c/dragonfly.zig b/lib/std/c/dragonfly.zig index 5a68ae4a5e..40fb9c8b83 100644 --- a/lib/std/c/dragonfly.zig +++ b/lib/std/c/dragonfly.zig @@ -13,46 +13,6 @@ pub extern "c" fn ptrace(request: c_int, pid: pid_t, addr: caddr_t, data: c_int) pub extern "c" fn umtx_sleep(ptr: *const volatile c_int, value: c_int, timeout: c_int) c_int; pub extern "c" fn umtx_wakeup(ptr: *const volatile c_int, count: c_int) c_int; -pub const mcontext_t = extern struct { - onstack: register_t, // XXX - sigcontext compat. - rdi: register_t, - rsi: register_t, - rdx: register_t, - rcx: register_t, - r8: register_t, - r9: register_t, - rax: register_t, - rbx: register_t, - rbp: register_t, - r10: register_t, - r11: register_t, - r12: register_t, - r13: register_t, - r14: register_t, - r15: register_t, - xflags: register_t, - trapno: register_t, - addr: register_t, - flags: register_t, - err: register_t, - rip: register_t, - cs: register_t, - rflags: register_t, - rsp: register_t, // machine state - ss: register_t, - - len: c_uint, // sizeof(mcontext_t) - fpformat: c_uint, - ownedfp: c_uint, - reserved: c_uint, - unused: [8]c_uint, - - // NOTE! 64-byte aligned as of here. Also must match savefpu structure. - fpregs: [256]c_int align(64), -}; - -pub const register_t = isize; - pub const E = enum(u16) { /// No error occurred. SUCCESS = 0, diff --git a/lib/std/c/freebsd.zig b/lib/std/c/freebsd.zig index c724c4ad88..45ad812ed1 100644 --- a/lib/std/c/freebsd.zig +++ b/lib/std/c/freebsd.zig @@ -29,7 +29,7 @@ comptime { pub extern "c" fn ptrace(request: c_int, pid: pid_t, addr: caddr_t, data: c_int) c_int; pub extern "c" fn kinfo_getfile(pid: pid_t, cntp: *c_int) ?[*]kinfo_file; -pub extern "c" fn copy_file_range(fd_in: fd_t, off_in: ?*off_t, fd_out: fd_t, off_out: ?*off_t, len: usize, flags: u32) usize; +pub extern "c" fn copy_file_range(fd_in: fd_t, off_in: ?*off_t, fd_out: fd_t, off_out: ?*off_t, len: usize, flags: u32) isize; pub extern "c" fn sendfile( in_fd: fd_t, diff --git a/lib/std/c/haiku.zig b/lib/std/c/haiku.zig index e62e387164..81cc3bc325 100644 --- a/lib/std/c/haiku.zig +++ b/lib/std/c/haiku.zig @@ -273,269 +273,6 @@ pub const E = enum(i32) { pub const status_t = i32; -pub const mcontext_t = switch (builtin.cpu.arch) { - .arm, .thumb => extern struct { - r0: u32, - r1: u32, - r2: u32, - r3: u32, - r4: u32, - r5: u32, - r6: u32, - r7: u32, - r8: u32, - r9: u32, - r10: u32, - r11: u32, - r12: u32, - r13: u32, - r14: u32, - r15: u32, - cpsr: u32, - }, - .aarch64 => extern struct { - x: [10]u64, - lr: u64, - sp: u64, - elr: u64, - spsr: u64, - fp_q: [32]u128, - fpsr: u32, - fpcr: u32, - }, - .m68k => extern struct { - pc: u32, - d0: u32, - d1: u32, - d2: u32, - d3: u32, - d4: u32, - d5: u32, - d6: u32, - d7: u32, - a0: u32, - a1: u32, - a2: u32, - a3: u32, - a4: u32, - a5: u32, - a6: u32, - a7: u32, - ccr: u8, - f0: f64, - f1: f64, - f2: f64, - f3: f64, - f4: f64, - f5: f64, - f6: f64, - f7: f64, - f8: f64, - f9: f64, - f10: f64, - f11: f64, - f12: f64, - f13: f64, - }, - .mipsel => extern struct { - r0: u32, - }, - .powerpc => extern struct { - pc: u32, - r0: u32, - r1: u32, - r2: u32, - r3: u32, - r4: u32, - r5: u32, - r6: u32, - r7: u32, - r8: u32, - r9: u32, - r10: u32, - r11: u32, - r12: u32, - f0: f64, - f1: f64, - f2: f64, - f3: f64, - f4: f64, - f5: f64, - f6: f64, - f7: f64, - f8: f64, - f9: f64, - f10: f64, - f11: f64, - f12: f64, - f13: f64, - reserved: u32, - fpscr: u32, - ctr: u32, - xer: u32, - cr: u32, - msr: u32, - lr: u32, - }, - .riscv64 => extern struct { - x: [31]u64, - pc: u64, - f: [32]f64, - fcsr: u64, - }, - .sparc64 => extern struct { - g1: u64, - g2: u64, - g3: u64, - g4: u64, - g5: u64, - g6: u64, - g7: u64, - o0: u64, - o1: u64, - o2: u64, - o3: u64, - o4: u64, - o5: u64, - sp: u64, - o7: u64, - l0: u64, - l1: u64, - l2: u64, - l3: u64, - l4: u64, - l5: u64, - l6: u64, - l7: u64, - i0: u64, - i1: u64, - i2: u64, - i3: u64, - i4: u64, - i5: u64, - fp: u64, - i7: u64, - }, - .x86 => extern struct { - pub const old_extended_regs = extern struct { - control: u16, - reserved1: u16, - status: u16, - reserved2: u16, - tag: u16, - reserved3: u16, - eip: u32, - cs: u16, - opcode: u16, - datap: u32, - ds: u16, - reserved4: u16, - fp_mmx: [8][10]u8, - }; - - pub const fp_register = extern struct { value: [10]u8, reserved: [6]u8 }; - - pub const xmm_register = extern struct { value: [16]u8 }; - - pub const new_extended_regs = extern struct { - control: u16, - status: u16, - tag: u16, - opcode: u16, - eip: u32, - cs: u16, - reserved1: u16, - datap: u32, - ds: u16, - reserved2: u16, - mxcsr: u32, - reserved3: u32, - fp_mmx: [8]fp_register, - xmmx: [8]xmm_register, - reserved4: [224]u8, - }; - - pub const extended_regs = extern struct { - state: extern union { - old_format: old_extended_regs, - new_format: new_extended_regs, - }, - format: u32, - }; - - eip: u32, - eflags: u32, - eax: u32, - ecx: u32, - edx: u32, - esp: u32, - ebp: u32, - reserved: u32, - xregs: extended_regs, - edi: u32, - esi: u32, - ebx: u32, - }, - .x86_64 => extern struct { - pub const fp_register = extern struct { - value: [10]u8, - reserved: [6]u8, - }; - - pub const xmm_register = extern struct { - value: [16]u8, - }; - - pub const fpu_state = extern struct { - control: u16, - status: u16, - tag: u16, - opcode: u16, - rip: u64, - rdp: u64, - mxcsr: u32, - mscsr_mask: u32, - - fp_mmx: [8]fp_register, - xmm: [16]xmm_register, - reserved: [96]u8, - }; - - pub const xstate_hdr = extern struct { - bv: u64, - xcomp_bv: u64, - reserved: [48]u8, - }; - - pub const savefpu = extern struct { - fxsave: fpu_state, - xstate: xstate_hdr, - ymm: [16]xmm_register, - }; - - rax: u64, - rbx: u64, - rcx: u64, - rdx: u64, - rdi: u64, - rsi: u64, - rbp: u64, - r8: u64, - r9: u64, - r10: u64, - r11: u64, - r12: u64, - r13: u64, - r14: u64, - r15: u64, - rsp: u64, - rip: u64, - rflags: u64, - fpu: savefpu, - }, - else => void, -}; - pub const DirEnt = extern struct { /// device dev: dev_t, diff --git a/lib/std/c/openbsd.zig b/lib/std/c/openbsd.zig index 27e60b530d..1dd65dca7b 100644 --- a/lib/std/c/openbsd.zig +++ b/lib/std/c/openbsd.zig @@ -144,53 +144,6 @@ pub const TCIO = enum(u32) { ION = 4, }; -pub const ucontext_t = switch (builtin.cpu.arch) { - .x86_64 => extern struct { - sc_rdi: c_long, - sc_rsi: c_long, - sc_rdx: c_long, - sc_rcx: c_long, - sc_r8: c_long, - sc_r9: c_long, - sc_r10: c_long, - sc_r11: c_long, - sc_r12: c_long, - sc_r13: c_long, - sc_r14: c_long, - sc_r15: c_long, - sc_rbp: c_long, - sc_rbx: c_long, - sc_rax: c_long, - sc_gs: c_long, - sc_fs: c_long, - sc_es: c_long, - sc_ds: c_long, - sc_trapno: c_long, - sc_err: c_long, - sc_rip: c_long, - sc_cs: c_long, - sc_rflags: c_long, - sc_rsp: c_long, - sc_ss: c_long, - - sc_fpstate: *anyopaque, // struct fxsave64 * - __sc_unused: c_int, - sc_mask: c_int, - sc_cookie: c_long, - }, - .aarch64 => extern struct { - __sc_unused: c_int, - sc_mask: c_int, - sc_sp: c_ulong, - sc_lr: c_ulong, - sc_elr: c_ulong, - sc_spsr: c_ulong, - sc_x: [30]c_ulong, - sc_cookie: c_long, - }, - else => @compileError("missing ucontext_t type definition"), -}; - pub const E = enum(u16) { /// No error occurred. SUCCESS = 0, diff --git a/lib/std/c/solaris.zig b/lib/std/c/solaris.zig index 39df093c86..e69c77dac7 100644 --- a/lib/std/c/solaris.zig +++ b/lib/std/c/solaris.zig @@ -31,29 +31,6 @@ pub const poolid_t = id_t; pub const zoneid_t = id_t; pub const ctid_t = id_t; -pub const fpregset_t = extern union { - regs: [130]u32, - chip_state: extern struct { - cw: u16, - sw: u16, - fctw: u8, - __fx_rsvd: u8, - fop: u16, - rip: u64, - rdp: u64, - mxcsr: u32, - mxcsr_mask: u32, - st: [8]extern union { - fpr_16: [5]u16, - __fpr_pad: u128, - }, - xmm: [16]u128, - __fx_ign2: [6]u128, - status: u32, - xstatus: u32, - }, -}; - pub const GETCONTEXT = 0; pub const SETCONTEXT = 1; pub const GETUSTACK = 2; diff --git a/lib/std/coff.zig b/lib/std/coff.zig index c0929020ec..1706af24ab 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -2,70 +2,9 @@ const std = @import("std.zig"); const assert = std.debug.assert; const mem = std.mem; -pub const CoffHeaderFlags = packed struct { - /// Image only, Windows CE, and Microsoft Windows NT and later. - /// This indicates that the file does not contain base relocations - /// and must therefore be loaded at its preferred base address. - /// If the base address is not available, the loader reports an error. - /// The default behavior of the linker is to strip base relocations - /// from executable (EXE) files. - RELOCS_STRIPPED: u1 = 0, - - /// Image only. This indicates that the image file is valid and can be run. - /// If this flag is not set, it indicates a linker error. - EXECUTABLE_IMAGE: u1 = 0, - - /// COFF line numbers have been removed. This flag is deprecated and should be zero. - LINE_NUMS_STRIPPED: u1 = 0, - - /// COFF symbol table entries for local symbols have been removed. - /// This flag is deprecated and should be zero. - LOCAL_SYMS_STRIPPED: u1 = 0, - - /// Obsolete. Aggressively trim working set. - /// This flag is deprecated for Windows 2000 and later and must be zero. - AGGRESSIVE_WS_TRIM: u1 = 0, - - /// Application can handle > 2-GB addresses. - LARGE_ADDRESS_AWARE: u1 = 0, - - /// This flag is reserved for future use. - RESERVED: u1 = 0, - - /// Little endian: the least significant bit (LSB) precedes the - /// most significant bit (MSB) in memory. This flag is deprecated and should be zero. - BYTES_REVERSED_LO: u1 = 0, - - /// Machine is based on a 32-bit-word architecture. - @"32BIT_MACHINE": u1 = 0, - - /// Debugging information is removed from the image file. - DEBUG_STRIPPED: u1 = 0, - - /// If the image is on removable media, fully load it and copy it to the swap file. - REMOVABLE_RUN_FROM_SWAP: u1 = 0, - - /// If the image is on network media, fully load it and copy it to the swap file. - NET_RUN_FROM_SWAP: u1 = 0, - - /// The image file is a system file, not a user program. - SYSTEM: u1 = 0, - - /// The image file is a dynamic-link library (DLL). - /// Such files are considered executable files for almost all purposes, - /// although they cannot be directly run. - DLL: u1 = 0, - - /// The file should be run only on a uniprocessor machine. - UP_SYSTEM_ONLY: u1 = 0, - - /// Big endian: the MSB precedes the LSB in memory. This flag is deprecated and should be zero. - BYTES_REVERSED_HI: u1 = 0, -}; - -pub const CoffHeader = extern struct { +pub const Header = extern struct { /// The number that identifies the type of target machine. - machine: MachineType, + machine: IMAGE.FILE.MACHINE, /// The number of sections. This indicates the size of the section table, which immediately follows the headers. number_of_sections: u16, @@ -88,49 +27,110 @@ pub const CoffHeader = extern struct { size_of_optional_header: u16, /// The flags that indicate the attributes of the file. - flags: CoffHeaderFlags, + flags: Header.Flags, + + pub const Flags = packed struct(u16) { + /// Image only, Windows CE, and Microsoft Windows NT and later. + /// This indicates that the file does not contain base relocations + /// and must therefore be loaded at its preferred base address. + /// If the base address is not available, the loader reports an error. + /// The default behavior of the linker is to strip base relocations + /// from executable (EXE) files. + RELOCS_STRIPPED: bool = false, + + /// Image only. This indicates that the image file is valid and can be run. + /// If this flag is not set, it indicates a linker error. + EXECUTABLE_IMAGE: bool = false, + + /// COFF line numbers have been removed. This flag is deprecated and should be zero. + LINE_NUMS_STRIPPED: bool = false, + + /// COFF symbol table entries for local symbols have been removed. + /// This flag is deprecated and should be zero. + LOCAL_SYMS_STRIPPED: bool = false, + + /// Obsolete. Aggressively trim working set. + /// This flag is deprecated for Windows 2000 and later and must be zero. + AGGRESSIVE_WS_TRIM: bool = false, + + /// Application can handle > 2-GB addresses. + LARGE_ADDRESS_AWARE: bool = false, + + /// This flag is reserved for future use. + RESERVED: bool = false, + + /// Little endian: the least significant bit (LSB) precedes the + /// most significant bit (MSB) in memory. This flag is deprecated and should be zero. + BYTES_REVERSED_LO: bool = false, + + /// Machine is based on a 32-bit-word architecture. + @"32BIT_MACHINE": bool = false, + + /// Debugging information is removed from the image file. + DEBUG_STRIPPED: bool = false, + + /// If the image is on removable media, fully load it and copy it to the swap file. + REMOVABLE_RUN_FROM_SWAP: bool = false, + + /// If the image is on network media, fully load it and copy it to the swap file. + NET_RUN_FROM_SWAP: bool = false, + + /// The image file is a system file, not a user program. + SYSTEM: bool = false, + + /// The image file is a dynamic-link library (DLL). + /// Such files are considered executable files for almost all purposes, + /// although they cannot be directly run. + DLL: bool = false, + + /// The file should be run only on a uniprocessor machine. + UP_SYSTEM_ONLY: bool = false, + + /// Big endian: the MSB precedes the LSB in memory. This flag is deprecated and should be zero. + BYTES_REVERSED_HI: bool = false, + }; }; // OptionalHeader.magic values // see https://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx -pub const IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b; -pub const IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b; +pub const IMAGE_NT_OPTIONAL_HDR32_MAGIC = @intFromEnum(OptionalHeader.Magic.PE32); +pub const IMAGE_NT_OPTIONAL_HDR64_MAGIC = @intFromEnum(OptionalHeader.Magic.@"PE32+"); -pub const DllFlags = packed struct { +pub const DllFlags = packed struct(u16) { _reserved_0: u5 = 0, /// Image can handle a high entropy 64-bit virtual address space. - HIGH_ENTROPY_VA: u1 = 0, + HIGH_ENTROPY_VA: bool = false, /// DLL can be relocated at load time. - DYNAMIC_BASE: u1 = 0, + DYNAMIC_BASE: bool = false, /// Code Integrity checks are enforced. - FORCE_INTEGRITY: u1 = 0, + FORCE_INTEGRITY: bool = false, /// Image is NX compatible. - NX_COMPAT: u1 = 0, + NX_COMPAT: bool = false, /// Isolation aware, but do not isolate the image. - NO_ISOLATION: u1 = 0, + NO_ISOLATION: bool = false, /// Does not use structured exception (SE) handling. No SE handler may be called in this image. - NO_SEH: u1 = 0, + NO_SEH: bool = false, /// Do not bind the image. - NO_BIND: u1 = 0, + NO_BIND: bool = false, /// Image must execute in an AppContainer. - APPCONTAINER: u1 = 0, + APPCONTAINER: bool = false, /// A WDM driver. - WDM_DRIVER: u1 = 0, + WDM_DRIVER: bool = false, /// Image supports Control Flow Guard. - GUARD_CF: u1 = 0, + GUARD_CF: bool = false, /// Terminal Server aware. - TERMINAL_SERVER_AWARE: u1 = 0, + TERMINAL_SERVER_AWARE: bool = false, }; pub const Subsystem = enum(u16) { @@ -180,7 +180,7 @@ pub const Subsystem = enum(u16) { }; pub const OptionalHeader = extern struct { - magic: u16, + magic: OptionalHeader.Magic, major_linker_version: u8, minor_linker_version: u8, size_of_code: u32, @@ -188,124 +188,67 @@ pub const OptionalHeader = extern struct { size_of_uninitialized_data: u32, address_of_entry_point: u32, base_of_code: u32, -}; -pub const OptionalHeaderPE32 = extern struct { - magic: u16, - major_linker_version: u8, - minor_linker_version: u8, - size_of_code: u32, - size_of_initialized_data: u32, - size_of_uninitialized_data: u32, - address_of_entry_point: u32, - base_of_code: u32, - base_of_data: u32, - image_base: u32, - section_alignment: u32, - file_alignment: u32, - major_operating_system_version: u16, - minor_operating_system_version: u16, - major_image_version: u16, - minor_image_version: u16, - major_subsystem_version: u16, - minor_subsystem_version: u16, - win32_version_value: u32, - size_of_image: u32, - size_of_headers: u32, - checksum: u32, - subsystem: Subsystem, - dll_flags: DllFlags, - size_of_stack_reserve: u32, - size_of_stack_commit: u32, - size_of_heap_reserve: u32, - size_of_heap_commit: u32, - loader_flags: u32, - number_of_rva_and_sizes: u32, -}; + pub const Magic = enum(u16) { + PE32 = 0x10b, + @"PE32+" = 0x20b, + _, + }; -pub const OptionalHeaderPE64 = extern struct { - magic: u16, - major_linker_version: u8, - minor_linker_version: u8, - size_of_code: u32, - size_of_initialized_data: u32, - size_of_uninitialized_data: u32, - address_of_entry_point: u32, - base_of_code: u32, - image_base: u64, - section_alignment: u32, - file_alignment: u32, - major_operating_system_version: u16, - minor_operating_system_version: u16, - major_image_version: u16, - minor_image_version: u16, - major_subsystem_version: u16, - minor_subsystem_version: u16, - win32_version_value: u32, - size_of_image: u32, - size_of_headers: u32, - checksum: u32, - subsystem: Subsystem, - dll_flags: DllFlags, - size_of_stack_reserve: u64, - size_of_stack_commit: u64, - size_of_heap_reserve: u64, - size_of_heap_commit: u64, - loader_flags: u32, - number_of_rva_and_sizes: u32, + pub const PE32 = extern struct { + standard: OptionalHeader, + base_of_data: u32, + image_base: u32, + section_alignment: u32, + file_alignment: u32, + major_operating_system_version: u16, + minor_operating_system_version: u16, + major_image_version: u16, + minor_image_version: u16, + major_subsystem_version: u16, + minor_subsystem_version: u16, + win32_version_value: u32, + size_of_image: u32, + size_of_headers: u32, + checksum: u32, + subsystem: Subsystem, + dll_flags: DllFlags, + size_of_stack_reserve: u32, + size_of_stack_commit: u32, + size_of_heap_reserve: u32, + size_of_heap_commit: u32, + loader_flags: u32, + number_of_rva_and_sizes: u32, + }; + + pub const @"PE32+" = extern struct { + standard: OptionalHeader, + image_base: u64, + section_alignment: u32, + file_alignment: u32, + major_operating_system_version: u16, + minor_operating_system_version: u16, + major_image_version: u16, + minor_image_version: u16, + major_subsystem_version: u16, + minor_subsystem_version: u16, + win32_version_value: u32, + size_of_image: u32, + size_of_headers: u32, + checksum: u32, + subsystem: Subsystem, + dll_flags: DllFlags, + size_of_stack_reserve: u64, + size_of_stack_commit: u64, + size_of_heap_reserve: u64, + size_of_heap_commit: u64, + loader_flags: u32, + number_of_rva_and_sizes: u32, + }; }; pub const IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16; -pub const DirectoryEntry = enum(u16) { - /// Export Directory - EXPORT = 0, - - /// Import Directory - IMPORT = 1, - - /// Resource Directory - RESOURCE = 2, - - /// Exception Directory - EXCEPTION = 3, - - /// Security Directory - SECURITY = 4, - - /// Base Relocation Table - BASERELOC = 5, - - /// Debug Directory - DEBUG = 6, - - /// Architecture Specific Data - ARCHITECTURE = 7, - - /// RVA of GP - GLOBALPTR = 8, - - /// TLS Directory - TLS = 9, - - /// Load Configuration Directory - LOAD_CONFIG = 10, - - /// Bound Import Directory in headers - BOUND_IMPORT = 11, - - /// Import Address Table - IAT = 12, - - /// Delay Load Import Descriptors - DELAY_IMPORT = 13, - - /// COM Runtime descriptor - COM_DESCRIPTOR = 14, - - _, -}; - pub const ImageDataDirectory = extern struct { virtual_address: u32, size: u32, @@ -319,7 +262,7 @@ pub const BaseRelocationDirectoryEntry = extern struct { block_size: u32, }; -pub const BaseRelocation = packed struct { +pub const BaseRelocation = packed struct(u16) { /// Stored in the remaining 12 bits of the WORD, an offset from the starting address that was specified in the Page RVA field for the block. /// This offset specifies where the base relocation is to be applied. offset: u12, @@ -447,12 +390,12 @@ pub const ImportDirectoryEntry = extern struct { }; pub const ImportLookupEntry32 = struct { - pub const ByName = packed struct { + pub const ByName = packed struct(u32) { name_table_rva: u31, flag: u1 = 0, }; - pub const ByOrdinal = packed struct { + pub const ByOrdinal = packed struct(u32) { ordinal_number: u16, unused: u15 = 0, flag: u1 = 1, @@ -472,13 +415,13 @@ pub const ImportLookupEntry32 = struct { }; pub const ImportLookupEntry64 = struct { - pub const ByName = packed struct { + pub const ByName = packed struct(u64) { name_table_rva: u31, unused: u32 = 0, flag: u1 = 0, }; - pub const ByOrdinal = packed struct { + pub const ByOrdinal = packed struct(u64) { ordinal_number: u16, unused: u47 = 0, flag: u1 = 1, @@ -519,7 +462,7 @@ pub const SectionHeader = extern struct { pointer_to_linenumbers: u32, number_of_relocations: u16, number_of_linenumbers: u16, - flags: SectionHeaderFlags, + flags: SectionHeader.Flags, pub fn getName(self: *align(1) const SectionHeader) ?[]const u8 { if (self.name[0] == '/') return null; @@ -536,119 +479,139 @@ pub const SectionHeader = extern struct { /// Applicable only to section headers in COFF objects. pub fn getAlignment(self: SectionHeader) ?u16 { - if (self.flags.ALIGN == 0) return null; - return std.math.powi(u16, 2, self.flags.ALIGN - 1) catch unreachable; + return self.flags.ALIGN.toByteUnits(); } pub fn setAlignment(self: *SectionHeader, new_alignment: u16) void { - assert(new_alignment > 0 and new_alignment <= 8192); - self.flags.ALIGN = @intCast(std.math.log2(new_alignment)); + self.flags.ALIGN = .fromByteUnits(new_alignment); } pub fn isCode(self: SectionHeader) bool { - return self.flags.CNT_CODE == 0b1; + return self.flags.CNT_CODE; } pub fn isComdat(self: SectionHeader) bool { - return self.flags.LNK_COMDAT == 0b1; + return self.flags.LNK_COMDAT; } -}; -pub const SectionHeaderFlags = packed struct { - _reserved_0: u3 = 0, + pub const Flags = packed struct(u32) { + SCALE_INDEX: bool = false, + + unused1: u2 = 0, - /// The section should not be padded to the next boundary. - /// This flag is obsolete and is replaced by IMAGE_SCN_ALIGN_1BYTES. - /// This is valid only for object files. - TYPE_NO_PAD: u1 = 0, + /// The section should not be padded to the next boundary. + /// This flag is obsolete and is replaced by `.ALIGN = .@"1BYTES"`. + /// This is valid only for object files. + TYPE_NO_PAD: bool = false, - _reserved_1: u1 = 0, + unused4: u1 = 0, - /// The section contains executable code. - CNT_CODE: u1 = 0, + /// The section contains executable code. + CNT_CODE: bool = false, - /// The section contains initialized data. - CNT_INITIALIZED_DATA: u1 = 0, + /// The section contains initialized data. + CNT_INITIALIZED_DATA: bool = false, - /// The section contains uninitialized data. - CNT_UNINITIALIZED_DATA: u1 = 0, + /// The section contains uninitialized data. + CNT_UNINITIALIZED_DATA: bool = false, - /// Reserved for future use. - LNK_OTHER: u1 = 0, + /// Reserved for future use. + LNK_OTHER: bool = false, - /// The section contains comments or other information. - /// The .drectve section has this type. - /// This is valid for object files only. - LNK_INFO: u1 = 0, + /// The section contains comments or other information. + /// The .drectve section has this type. + /// This is valid for object files only. + LNK_INFO: bool = false, - _reserved_2: u1 = 0, + unused10: u1 = 0, - /// The section will not become part of the image. - /// This is valid only for object files. - LNK_REMOVE: u1 = 0, + /// The section will not become part of the image. + /// This is valid only for object files. + LNK_REMOVE: bool = false, - /// The section contains COMDAT data. - /// For more information, see COMDAT Sections (Object Only). - /// This is valid only for object files. - LNK_COMDAT: u1 = 0, + /// The section contains COMDAT data. + /// For more information, see COMDAT Sections (Object Only). + /// This is valid only for object files. + LNK_COMDAT: bool = false, - _reserved_3: u2 = 0, + unused13: u2 = 0, - /// The section contains data referenced through the global pointer (GP). - GPREL: u1 = 0, + union14: packed union { + mask: u1, + /// The section contains data referenced through the global pointer (GP). + GPREL: bool, + MEM_FARDATA: bool, + } = .{ .mask = 0 }, - /// Reserved for future use. - MEM_PURGEABLE: u1 = 0, + unused15: u1 = 0, - /// Reserved for future use. - MEM_16BIT: u1 = 0, + union16: packed union { + mask: u1, + MEM_PURGEABLE: bool, + MEM_16BIT: bool, + } = .{ .mask = 0 }, - /// Reserved for future use. - MEM_LOCKED: u1 = 0, + /// Reserved for future use. + MEM_LOCKED: bool = false, - /// Reserved for future use. - MEM_PRELOAD: u1 = 0, + /// Reserved for future use. + MEM_PRELOAD: bool = false, - /// Takes on multiple values according to flags: - /// pub const IMAGE_SCN_ALIGN_1BYTES: u32 = 0x100000; - /// pub const IMAGE_SCN_ALIGN_2BYTES: u32 = 0x200000; - /// pub const IMAGE_SCN_ALIGN_4BYTES: u32 = 0x300000; - /// pub const IMAGE_SCN_ALIGN_8BYTES: u32 = 0x400000; - /// pub const IMAGE_SCN_ALIGN_16BYTES: u32 = 0x500000; - /// pub const IMAGE_SCN_ALIGN_32BYTES: u32 = 0x600000; - /// pub const IMAGE_SCN_ALIGN_64BYTES: u32 = 0x700000; - /// pub const IMAGE_SCN_ALIGN_128BYTES: u32 = 0x800000; - /// pub const IMAGE_SCN_ALIGN_256BYTES: u32 = 0x900000; - /// pub const IMAGE_SCN_ALIGN_512BYTES: u32 = 0xA00000; - /// pub const IMAGE_SCN_ALIGN_1024BYTES: u32 = 0xB00000; - /// pub const IMAGE_SCN_ALIGN_2048BYTES: u32 = 0xC00000; - /// pub const IMAGE_SCN_ALIGN_4096BYTES: u32 = 0xD00000; - /// pub const IMAGE_SCN_ALIGN_8192BYTES: u32 = 0xE00000; - ALIGN: u4 = 0, + ALIGN: SectionHeader.Flags.Align = .NONE, - /// The section contains extended relocations. - LNK_NRELOC_OVFL: u1 = 0, + /// The section contains extended relocations. + LNK_NRELOC_OVFL: bool = false, - /// The section can be discarded as needed. - MEM_DISCARDABLE: u1 = 0, + /// The section can be discarded as needed. + MEM_DISCARDABLE: bool = false, - /// The section cannot be cached. - MEM_NOT_CACHED: u1 = 0, + /// The section cannot be cached. + MEM_NOT_CACHED: bool = false, - /// The section is not pageable. - MEM_NOT_PAGED: u1 = 0, + /// The section is not pageable. + MEM_NOT_PAGED: bool = false, - /// The section can be shared in memory. - MEM_SHARED: u1 = 0, + /// The section can be shared in memory. + MEM_SHARED: bool = false, - /// The section can be executed as code. - MEM_EXECUTE: u1 = 0, + /// The section can be executed as code. + MEM_EXECUTE: bool = false, - /// The section can be read. - MEM_READ: u1 = 0, + /// The section can be read. + MEM_READ: bool = false, - /// The section can be written to. - MEM_WRITE: u1 = 0, + /// The section can be written to. + MEM_WRITE: bool = false, + + pub const Align = enum(u4) { + NONE = 0, + @"1BYTES" = 1, + @"2BYTES" = 2, + @"4BYTES" = 3, + @"8BYTES" = 4, + @"16BYTES" = 5, + @"32BYTES" = 6, + @"64BYTES" = 7, + @"128BYTES" = 8, + @"256BYTES" = 9, + @"512BYTES" = 10, + @"1024BYTES" = 11, + @"2048BYTES" = 12, + @"4096BYTES" = 13, + @"8192BYTES" = 14, + _, + + pub fn toByteUnits(a: Align) ?u16 { + if (a == .NONE) return null; + return @as(u16, 1) << (@intFromEnum(a) - 1); + } + + pub fn fromByteUnits(n: u16) Align { + std.debug.assert(std.math.isPowerOfTwo(n)); + return @enumFromInt(@ctz(n) + 1); + } + }; + }; }; pub const Symbol = struct { @@ -691,7 +654,7 @@ pub const SectionNumber = enum(u16) { _, }; -pub const SymType = packed struct { +pub const SymType = packed struct(u16) { complex_type: ComplexType, base_type: BaseType, }; @@ -921,6 +884,10 @@ pub const WeakExternalDefinition = struct { flag: WeakExternalFlag, unused: [10]u8, + + pub fn sizeOf() usize { + return 18; + } }; // https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/km/ntimage.h @@ -982,87 +949,7 @@ pub const DebugInfoDefinition = struct { unused_3: [2]u8, }; -pub const MachineType = enum(u16) { - UNKNOWN = 0x0, - /// Alpha AXP, 32-bit address space - ALPHA = 0x184, - /// Alpha 64, 64-bit address space - ALPHA64 = 0x284, - /// Matsushita AM33 - AM33 = 0x1d3, - /// x64 - X64 = 0x8664, - /// ARM little endian - ARM = 0x1c0, - /// ARM64 little endian - ARM64 = 0xaa64, - /// ARM64EC - ARM64EC = 0xa641, - /// ARM64X - ARM64X = 0xa64e, - /// ARM Thumb-2 little endian - ARMNT = 0x1c4, - /// CEE - CEE = 0xc0ee, - /// CEF - CEF = 0xcef, - /// Hybrid PE - CHPE_X86 = 0x3a64, - /// EFI byte code - EBC = 0xebc, - /// Intel 386 or later processors and compatible processors - I386 = 0x14c, - /// Intel Itanium processor family - IA64 = 0x200, - /// LoongArch32 - LOONGARCH32 = 0x6232, - /// LoongArch64 - LOONGARCH64 = 0x6264, - /// Mitsubishi M32R little endian - M32R = 0x9041, - /// MIPS16 - MIPS16 = 0x266, - /// MIPS with FPU - MIPSFPU = 0x366, - /// MIPS16 with FPU - MIPSFPU16 = 0x466, - /// Power PC little endian - POWERPC = 0x1f0, - /// Power PC with floating point support - POWERPCFP = 0x1f1, - /// MIPS little endian - R3000 = 0x162, - /// MIPS little endian - R4000 = 0x166, - /// MIPS little endian - R10000 = 0x168, - /// RISC-V 32-bit address space - RISCV32 = 0x5032, - /// RISC-V 64-bit address space - RISCV64 = 0x5064, - /// RISC-V 128-bit address space - RISCV128 = 0x5128, - /// Hitachi SH3 - SH3 = 0x1a2, - /// Hitachi SH3 DSP - SH3DSP = 0x1a3, - /// SH3E little-endian - SH3E = 0x1a4, - /// Hitachi SH4 - SH4 = 0x1a6, - /// Hitachi SH5 - SH5 = 0x1a8, - /// Thumb - THUMB = 0x1c2, - /// Infineon - TRICORE = 0x520, - /// MIPS little-endian WCE v2 - WCEMIPSV2 = 0x169, - - _, -}; - -pub const CoffError = error{ +pub const Error = error{ InvalidPEMagic, InvalidPEHeader, InvalidMachine, @@ -1083,27 +970,28 @@ pub const Coff = struct { age: u32 = undefined, // The lifetime of `data` must be longer than the lifetime of the returned Coff - pub fn init(data: []const u8, is_loaded: bool) !Coff { + pub fn init(data: []const u8, is_loaded: bool) error{ EndOfStream, MissingPEHeader }!Coff { const pe_pointer_offset = 0x3C; const pe_magic = "PE\x00\x00"; - var reader: std.Io.Reader = .fixed(data); - reader.seek = pe_pointer_offset; - const coff_header_offset = try reader.takeInt(u32, .little); - reader.seek = coff_header_offset; - const is_image = mem.eql(u8, pe_magic, try reader.takeArray(4)); + if (data.len < pe_pointer_offset + 4) return error.EndOfStream; + const header_offset = mem.readInt(u32, data[pe_pointer_offset..][0..4], .little); + if (data.len < header_offset + 4) return error.EndOfStream; + const is_image = mem.eql(u8, data[header_offset..][0..4], pe_magic); - var coff = @This(){ + const coff: Coff = .{ .data = data, .is_image = is_image, .is_loaded = is_loaded, - .coff_header_offset = coff_header_offset, + .coff_header_offset = o: { + if (is_image) break :o header_offset + 4; + break :o header_offset; + }, }; // Do some basic validation upfront if (is_image) { - coff.coff_header_offset = coff.coff_header_offset + 4; - const coff_header = coff.getCoffHeader(); + const coff_header = coff.getHeader(); if (coff_header.size_of_optional_header == 0) return error.MissingPEHeader; } @@ -1117,9 +1005,9 @@ pub const Coff = struct { assert(self.is_image); const data_dirs = self.getDataDirectories(); - if (@intFromEnum(DirectoryEntry.DEBUG) >= data_dirs.len) return null; + if (@intFromEnum(IMAGE.DIRECTORY_ENTRY.DEBUG) >= data_dirs.len) return null; - const debug_dir = data_dirs[@intFromEnum(DirectoryEntry.DEBUG)]; + const debug_dir = data_dirs[@intFromEnum(IMAGE.DIRECTORY_ENTRY.DEBUG)]; var reader: std.Io.Reader = .fixed(self.data); if (self.is_loaded) { @@ -1160,31 +1048,31 @@ pub const Coff = struct { return self.data[start .. start + len]; } - pub fn getCoffHeader(self: Coff) CoffHeader { - return @as(*align(1) const CoffHeader, @ptrCast(self.data[self.coff_header_offset..][0..@sizeOf(CoffHeader)])).*; + pub fn getHeader(self: Coff) Header { + return @as(*align(1) const Header, @ptrCast(self.data[self.coff_header_offset..][0..@sizeOf(Header)])).*; } pub fn getOptionalHeader(self: Coff) OptionalHeader { assert(self.is_image); - const offset = self.coff_header_offset + @sizeOf(CoffHeader); + const offset = self.coff_header_offset + @sizeOf(Header); return @as(*align(1) const OptionalHeader, @ptrCast(self.data[offset..][0..@sizeOf(OptionalHeader)])).*; } - pub fn getOptionalHeader32(self: Coff) OptionalHeaderPE32 { + pub fn getOptionalHeader32(self: Coff) OptionalHeader.PE32 { assert(self.is_image); - const offset = self.coff_header_offset + @sizeOf(CoffHeader); - return @as(*align(1) const OptionalHeaderPE32, @ptrCast(self.data[offset..][0..@sizeOf(OptionalHeaderPE32)])).*; + const offset = self.coff_header_offset + @sizeOf(Header); + return @as(*align(1) const OptionalHeader.PE32, @ptrCast(self.data[offset..][0..@sizeOf(OptionalHeader.PE32)])).*; } - pub fn getOptionalHeader64(self: Coff) OptionalHeaderPE64 { + pub fn getOptionalHeader64(self: Coff) OptionalHeader.@"PE32+" { assert(self.is_image); - const offset = self.coff_header_offset + @sizeOf(CoffHeader); - return @as(*align(1) const OptionalHeaderPE64, @ptrCast(self.data[offset..][0..@sizeOf(OptionalHeaderPE64)])).*; + const offset = self.coff_header_offset + @sizeOf(Header); + return @as(*align(1) const OptionalHeader.@"PE32+", @ptrCast(self.data[offset..][0..@sizeOf(OptionalHeader.@"PE32+")])).*; } pub fn getImageBase(self: Coff) u64 { const hdr = self.getOptionalHeader(); - return switch (hdr.magic) { + return switch (@intFromEnum(hdr.magic)) { IMAGE_NT_OPTIONAL_HDR32_MAGIC => self.getOptionalHeader32().image_base, IMAGE_NT_OPTIONAL_HDR64_MAGIC => self.getOptionalHeader64().image_base, else => unreachable, // We assume we have validated the header already @@ -1193,7 +1081,7 @@ pub const Coff = struct { pub fn getNumberOfDataDirectories(self: Coff) u32 { const hdr = self.getOptionalHeader(); - return switch (hdr.magic) { + return switch (@intFromEnum(hdr.magic)) { IMAGE_NT_OPTIONAL_HDR32_MAGIC => self.getOptionalHeader32().number_of_rva_and_sizes, IMAGE_NT_OPTIONAL_HDR64_MAGIC => self.getOptionalHeader64().number_of_rva_and_sizes, else => unreachable, // We assume we have validated the header already @@ -1202,17 +1090,17 @@ pub const Coff = struct { pub fn getDataDirectories(self: *const Coff) []align(1) const ImageDataDirectory { const hdr = self.getOptionalHeader(); - const size: usize = switch (hdr.magic) { - IMAGE_NT_OPTIONAL_HDR32_MAGIC => @sizeOf(OptionalHeaderPE32), - IMAGE_NT_OPTIONAL_HDR64_MAGIC => @sizeOf(OptionalHeaderPE64), + const size: usize = switch (@intFromEnum(hdr.magic)) { + IMAGE_NT_OPTIONAL_HDR32_MAGIC => @sizeOf(OptionalHeader.PE32), + IMAGE_NT_OPTIONAL_HDR64_MAGIC => @sizeOf(OptionalHeader.@"PE32+"), else => unreachable, // We assume we have validated the header already }; - const offset = self.coff_header_offset + @sizeOf(CoffHeader) + size; + const offset = self.coff_header_offset + @sizeOf(Header) + size; return @as([*]align(1) const ImageDataDirectory, @ptrCast(self.data[offset..]))[0..self.getNumberOfDataDirectories()]; } pub fn getSymtab(self: *const Coff) ?Symtab { - const coff_header = self.getCoffHeader(); + const coff_header = self.getHeader(); if (coff_header.pointer_to_symbol_table == 0) return null; const offset = coff_header.pointer_to_symbol_table; @@ -1221,7 +1109,7 @@ pub const Coff = struct { } pub fn getStrtab(self: *const Coff) error{InvalidStrtabSize}!?Strtab { - const coff_header = self.getCoffHeader(); + const coff_header = self.getHeader(); if (coff_header.pointer_to_symbol_table == 0) return null; const offset = coff_header.pointer_to_symbol_table + Symbol.sizeOf() * coff_header.number_of_symbols; @@ -1237,8 +1125,8 @@ pub const Coff = struct { } pub fn getSectionHeaders(self: *const Coff) []align(1) const SectionHeader { - const coff_header = self.getCoffHeader(); - const offset = self.coff_header_offset + @sizeOf(CoffHeader) + coff_header.size_of_optional_header; + const coff_header = self.getHeader(); + const offset = self.coff_header_offset + @sizeOf(Header) + coff_header.size_of_optional_header; return @as([*]align(1) const SectionHeader, @ptrCast(self.data.ptr + offset))[0..coff_header.number_of_sections]; } @@ -1413,14 +1301,16 @@ pub const Strtab = struct { }; pub const ImportHeader = extern struct { - sig1: MachineType, - sig2: u16, + /// Must be IMAGE_FILE_MACHINE_UNKNOWN + sig1: IMAGE.FILE.MACHINE = .UNKNOWN, + /// Must be 0xFFFF + sig2: u16 = 0xFFFF, version: u16, - machine: MachineType, + machine: IMAGE.FILE.MACHINE, time_date_stamp: u32, size_of_data: u32, hint: u16, - types: packed struct { + types: packed struct(u16) { type: ImportType, name_type: ImportNameType, reserved: u11, @@ -1460,119 +1350,572 @@ pub const Relocation = extern struct { type: u16, }; -pub const ImageRelAmd64 = enum(u16) { - /// The relocation is ignored. - absolute = 0, - - /// The 64-bit VA of the relocation target. - addr64 = 1, - - /// The 32-bit VA of the relocation target. - addr32 = 2, - - /// The 32-bit address without an image base. - addr32nb = 3, - - /// The 32-bit relative address from the byte following the relocation. - rel32 = 4, - - /// The 32-bit address relative to byte distance 1 from the relocation. - rel32_1 = 5, - - /// The 32-bit address relative to byte distance 2 from the relocation. - rel32_2 = 6, - - /// The 32-bit address relative to byte distance 3 from the relocation. - rel32_3 = 7, - - /// The 32-bit address relative to byte distance 4 from the relocation. - rel32_4 = 8, - - /// The 32-bit address relative to byte distance 5 from the relocation. - rel32_5 = 9, - - /// The 16-bit section index of the section that contains the target. - /// This is used to support debugging information. - section = 10, - - /// The 32-bit offset of the target from the beginning of its section. - /// This is used to support debugging information and static thread local storage. - secrel = 11, - - /// A 7-bit unsigned offset from the base of the section that contains the target. - secrel7 = 12, - - /// CLR tokens. - token = 13, - - /// A 32-bit signed span-dependent value emitted into the object. - srel32 = 14, - - /// A pair that must immediately follow every span-dependent value. - pair = 15, - - /// A 32-bit signed span-dependent value that is applied at link time. - sspan32 = 16, - - _, -}; - -pub const ImageRelArm64 = enum(u16) { - /// The relocation is ignored. - absolute = 0, - - /// The 32-bit VA of the target. - addr32 = 1, - - /// The 32-bit RVA of the target. - addr32nb = 2, - - /// The 26-bit relative displacement to the target, for B and BL instructions. - branch26 = 3, - - /// The page base of the target, for ADRP instruction. - pagebase_rel21 = 4, - - /// The 21-bit relative displacement to the target, for instruction ADR. - rel21 = 5, - - /// The 12-bit page offset of the target, for instructions ADD/ADDS (immediate) with zero shift. - pageoffset_12a = 6, - - /// The 12-bit page offset of the target, for instruction LDR (indexed, unsigned immediate). - pageoffset_12l = 7, - - /// The 32-bit offset of the target from the beginning of its section. - /// This is used to support debugging information and static thread local storage. - secrel = 8, +pub const IMAGE = struct { + pub const DIRECTORY_ENTRY = enum(u32) { + /// Export Directory + EXPORT = 0, + /// Import Directory + IMPORT = 1, + /// Resource Directory + RESOURCE = 2, + /// Exception Directory + EXCEPTION = 3, + /// Security Directory + SECURITY = 4, + /// Base Relocation Table + BASERELOC = 5, + /// Debug Directory + DEBUG = 6, + /// Architecture Specific Data + ARCHITECTURE = 7, + /// RVA of GP + GLOBALPTR = 8, + /// TLS Directory + TLS = 9, + /// Load Configuration Directory + LOAD_CONFIG = 10, + /// Bound Import Directory in headers + BOUND_IMPORT = 11, + /// Import Address Table + IAT = 12, + /// Delay Load Import Descriptors + DELAY_IMPORT = 13, + /// COM Runtime descriptor + COM_DESCRIPTOR = 14, + /// must be zero + RESERVED = 15, + _, + + pub const len = @typeInfo(IMAGE.DIRECTORY_ENTRY).@"enum".fields.len; + }; - /// Bit 0:11 of section offset of the target for instructions ADD/ADDS (immediate) with zero shift. - low12a = 9, + pub const FILE = struct { + /// Machine Types + /// The Machine field has one of the following values, which specify the CPU type. + /// An image file can be run only on the specified machine or on a system that emulates the specified machine. + pub const MACHINE = enum(u16) { + /// The content of this field is assumed to be applicable to any machine type + UNKNOWN = 0x0, + /// Alpha AXP, 32-bit address space + ALPHA = 0x184, + /// Alpha 64, 64-bit address space + ALPHA64 = 0x284, + /// Matsushita AM33 + AM33 = 0x1d3, + /// x64 + AMD64 = 0x8664, + /// ARM little endian + ARM = 0x1c0, + /// ARM64 little endian + ARM64 = 0xaa64, + /// ABI that enables interoperability between native ARM64 and emulated x64 code. + ARM64EC = 0xA641, + /// Binary format that allows both native ARM64 and ARM64EC code to coexist in the same file. + ARM64X = 0xA64E, + /// ARM Thumb-2 little endian + ARMNT = 0x1c4, + /// EFI byte code + EBC = 0xebc, + /// Intel 386 or later processors and compatible processors + I386 = 0x14c, + /// Intel Itanium processor family + IA64 = 0x200, + /// LoongArch 32-bit processor family + LOONGARCH32 = 0x6232, + /// LoongArch 64-bit processor family + LOONGARCH64 = 0x6264, + /// Mitsubishi M32R little endian + M32R = 0x9041, + /// MIPS16 + MIPS16 = 0x266, + /// MIPS with FPU + MIPSFPU = 0x366, + /// MIPS16 with FPU + MIPSFPU16 = 0x466, + /// Power PC little endian + POWERPC = 0x1f0, + /// Power PC with floating point support + POWERPCFP = 0x1f1, + /// MIPS I compatible 32-bit big endian + R3000BE = 0x160, + /// MIPS I compatible 32-bit little endian + R3000 = 0x162, + /// MIPS III compatible 64-bit little endian + R4000 = 0x166, + /// MIPS IV compatible 64-bit little endian + R10000 = 0x168, + /// RISC-V 32-bit address space + RISCV32 = 0x5032, + /// RISC-V 64-bit address space + RISCV64 = 0x5064, + /// RISC-V 128-bit address space + RISCV128 = 0x5128, + /// Hitachi SH3 + SH3 = 0x1a2, + /// Hitachi SH3 DSP + SH3DSP = 0x1a3, + /// Hitachi SH4 + SH4 = 0x1a6, + /// Hitachi SH5 + SH5 = 0x1a8, + /// Thumb + THUMB = 0x1c2, + /// MIPS little-endian WCE v2 + WCEMIPSV2 = 0x169, + _, + /// AXP 64 (Same as Alpha 64) + pub const AXP64: IMAGE.FILE.MACHINE = .ALPHA64; + }; + }; - /// Bit 12:23 of section offset of the target, for instructions ADD/ADDS (immediate) with zero shift. - high12a = 10, + pub const REL = struct { + /// x64 Processors + /// The following relocation type indicators are defined for x64 and compatible processors. + pub const AMD64 = enum(u16) { + /// The relocation is ignored. + ABSOLUTE = 0x0000, + /// The 64-bit VA of the relocation target. + ADDR64 = 0x0001, + /// The 32-bit VA of the relocation target. + ADDR32 = 0x0002, + /// The 32-bit address without an image base (RVA). + ADDR32NB = 0x0003, + /// The 32-bit relative address from the byte following the relocation. + REL32 = 0x0004, + /// The 32-bit address relative to byte distance 1 from the relocation. + REL32_1 = 0x0005, + /// The 32-bit address relative to byte distance 2 from the relocation. + REL32_2 = 0x0006, + /// The 32-bit address relative to byte distance 3 from the relocation. + REL32_3 = 0x0007, + /// The 32-bit address relative to byte distance 4 from the relocation. + REL32_4 = 0x0008, + /// The 32-bit address relative to byte distance 5 from the relocation. + REL32_5 = 0x0009, + /// The 16-bit section index of the section that contains the target. + /// This is used to support debugging information. + SECTION = 0x000A, + /// The 32-bit offset of the target from the beginning of its section. + /// This is used to support debugging information and static thread local storage. + SECREL = 0x000B, + /// A 7-bit unsigned offset from the base of the section that contains the target. + SECREL7 = 0x000C, + /// CLR tokens. + TOKEN = 0x000D, + /// A 32-bit signed span-dependent value emitted into the object. + SREL32 = 0x000E, + /// A pair that must immediately follow every span-dependent value. + PAIR = 0x000F, + /// A 32-bit signed span-dependent value that is applied at link time. + SSPAN32 = 0x0010, + _, + }; - /// Bit 0:11 of section offset of the target, for instruction LDR (indexed, unsigned immediate). - low12l = 11, + /// ARM Processors + /// The following relocation type indicators are defined for ARM processors. + pub const ARM = enum(u16) { + /// The relocation is ignored. + ABSOLUTE = 0x0000, + /// The 32-bit VA of the target. + ADDR32 = 0x0001, + /// The 32-bit RVA of the target. + ADDR32NB = 0x0002, + /// The 24-bit relative displacement to the target. + BRANCH24 = 0x0003, + /// The reference to a subroutine call. + /// The reference consists of two 16-bit instructions with 11-bit offsets. + BRANCH11 = 0x0004, + /// The 32-bit relative address from the byte following the relocation. + REL32 = 0x000A, + /// The 16-bit section index of the section that contains the target. + /// This is used to support debugging information. + SECTION = 0x000E, + /// The 32-bit offset of the target from the beginning of its section. + /// This is used to support debugging information and static thread local storage. + SECREL = 0x000F, + /// The 32-bit VA of the target. + /// This relocation is applied using a MOVW instruction for the low 16 bits followed by a MOVT for the high 16 bits. + MOV32 = 0x0010, + /// The 32-bit VA of the target. + /// This relocation is applied using a MOVW instruction for the low 16 bits followed by a MOVT for the high 16 bits. + THUMB_MOV32 = 0x0011, + /// The instruction is fixed up with the 21-bit relative displacement to the 2-byte aligned target. + /// The least significant bit of the displacement is always zero and is not stored. + /// This relocation corresponds to a Thumb-2 32-bit conditional B instruction. + THUMB_BRANCH20 = 0x0012, + Unused = 0x0013, + /// The instruction is fixed up with the 25-bit relative displacement to the 2-byte aligned target. + /// The least significant bit of the displacement is zero and is not stored.This relocation corresponds to a Thumb-2 B instruction. + THUMB_BRANCH24 = 0x0014, + /// The instruction is fixed up with the 25-bit relative displacement to the 4-byte aligned target. + /// The low 2 bits of the displacement are zero and are not stored. + /// This relocation corresponds to a Thumb-2 BLX instruction. + THUMB_BLX23 = 0x0015, + /// The relocation is valid only when it immediately follows a ARM_REFHI or THUMB_REFHI. + /// Its SymbolTableIndex contains a displacement and not an index into the symbol table. + PAIR = 0x0016, + _, + }; - /// CLR token. - token = 12, + /// ARM64 Processors + /// The following relocation type indicators are defined for ARM64 processors. + pub const ARM64 = enum(u16) { + /// The relocation is ignored. + ABSOLUTE = 0x0000, + /// The 32-bit VA of the target. + ADDR32 = 0x0001, + /// The 32-bit RVA of the target. + ADDR32NB = 0x0002, + /// The 26-bit relative displacement to the target, for B and BL instructions. + BRANCH26 = 0x0003, + /// The page base of the target, for ADRP instruction. + PAGEBASE_REL21 = 0x0004, + /// The 12-bit relative displacement to the target, for instruction ADR + REL21 = 0x0005, + /// The 12-bit page offset of the target, for instructions ADD/ADDS (immediate) with zero shift. + PAGEOFFSET_12A = 0x0006, + /// The 12-bit page offset of the target, for instruction LDR (indexed, unsigned immediate). + PAGEOFFSET_12L = 0x0007, + /// The 32-bit offset of the target from the beginning of its section. + /// This is used to support debugging information and static thread local storage. + SECREL = 0x0008, + /// Bit 0:11 of section offset of the target, for instructions ADD/ADDS (immediate) with zero shift. + SECREL_LOW12A = 0x0009, + /// Bit 12:23 of section offset of the target, for instructions ADD/ADDS (immediate) with zero shift. + SECREL_HIGH12A = 0x000A, + /// Bit 0:11 of section offset of the target, for instruction LDR (indexed, unsigned immediate). + SECREL_LOW12L = 0x000B, + /// CLR token. + TOKEN = 0x000C, + /// The 16-bit section index of the section that contains the target. + /// This is used to support debugging information. + SECTION = 0x000D, + /// The 64-bit VA of the relocation target. + ADDR64 = 0x000E, + /// The 19-bit offset to the relocation target, for conditional B instruction. + BRANCH19 = 0x000F, + /// The 14-bit offset to the relocation target, for instructions TBZ and TBNZ. + BRANCH14 = 0x0010, + /// The 32-bit relative address from the byte following the relocation. + REL32 = 0x0011, + _, + }; - /// The 16-bit section index of the section that contains the target. - /// This is used to support debugging information. - section = 13, + /// Hitachi SuperH Processors + /// The following relocation type indicators are defined for SH3 and SH4 processors. + /// SH5-specific relocations are noted as SHM (SH Media). + pub const SH = enum(u16) { + /// The relocation is ignored. + @"3_ABSOLUTE" = 0x0000, + /// A reference to the 16-bit location that contains the VA of the target symbol. + @"3_DIRECT16" = 0x0001, + /// The 32-bit VA of the target symbol. + @"3_DIRECT32" = 0x0002, + /// A reference to the 8-bit location that contains the VA of the target symbol. + @"3_DIRECT8" = 0x0003, + /// A reference to the 8-bit instruction that contains the effective 16-bit VA of the target symbol. + @"3_DIRECT8_WORD" = 0x0004, + /// A reference to the 8-bit instruction that contains the effective 32-bit VA of the target symbol. + @"3_DIRECT8_LONG" = 0x0005, + /// A reference to the 8-bit location whose low 4 bits contain the VA of the target symbol. + @"3_DIRECT4" = 0x0006, + /// A reference to the 8-bit instruction whose low 4 bits contain the effective 16-bit VA of the target symbol. + @"3_DIRECT4_WORD" = 0x0007, + /// A reference to the 8-bit instruction whose low 4 bits contain the effective 32-bit VA of the target symbol. + @"3_DIRECT4_LONG" = 0x0008, + /// A reference to the 8-bit instruction that contains the effective 16-bit relative offset of the target symbol. + @"3_PCREL8_WORD" = 0x0009, + /// A reference to the 8-bit instruction that contains the effective 32-bit relative offset of the target symbol. + @"3_PCREL8_LONG" = 0x000A, + /// A reference to the 16-bit instruction whose low 12 bits contain the effective 16-bit relative offset of the target symbol. + @"3_PCREL12_WORD" = 0x000B, + /// A reference to a 32-bit location that is the VA of the section that contains the target symbol. + @"3_STARTOF_SECTION" = 0x000C, + /// A reference to the 32-bit location that is the size of the section that contains the target symbol. + @"3_SIZEOF_SECTION" = 0x000D, + /// The 16-bit section index of the section that contains the target. + /// This is used to support debugging information. + @"3_SECTION" = 0x000E, + /// The 32-bit offset of the target from the beginning of its section. + /// This is used to support debugging information and static thread local storage. + @"3_SECREL" = 0x000F, + /// The 32-bit RVA of the target symbol. + @"3_DIRECT32_NB" = 0x0010, + /// GP relative. + @"3_GPREL4_LONG" = 0x0011, + /// CLR token. + @"3_TOKEN" = 0x0012, + /// The offset from the current instruction in longwords. + /// If the NOMODE bit is not set, insert the inverse of the low bit at bit 32 to select PTA or PTB. + M_PCRELPT = 0x0013, + /// The low 16 bits of the 32-bit address. + M_REFLO = 0x0014, + /// The high 16 bits of the 32-bit address. + M_REFHALF = 0x0015, + /// The low 16 bits of the relative address. + M_RELLO = 0x0016, + /// The high 16 bits of the relative address. + M_RELHALF = 0x0017, + /// The relocation is valid only when it immediately follows a REFHALF, RELHALF, or RELLO relocation. + /// The SymbolTableIndex field of the relocation contains a displacement and not an index into the symbol table. + M_PAIR = 0x0018, + /// The relocation ignores section mode. + M_NOMODE = 0x8000, + _, + }; - /// The 64-bit VA of the relocation target. - addr64 = 14, + /// IBM PowerPC Processors + /// The following relocation type indicators are defined for PowerPC processors. + pub const PPC = enum(u16) { + /// The relocation is ignored. + ABSOLUTE = 0x0000, + /// The 64-bit VA of the target. + ADDR64 = 0x0001, + /// The 32-bit VA of the target. + ADDR32 = 0x0002, + /// The low 24 bits of the VA of the target. + /// This is valid only when the target symbol is absolute and can be sign-extended to its original value. + ADDR24 = 0x0003, + /// The low 16 bits of the target's VA. + ADDR16 = 0x0004, + /// The low 14 bits of the target's VA. + /// This is valid only when the target symbol is absolute and can be sign-extended to its original value. + ADDR14 = 0x0005, + /// A 24-bit PC-relative offset to the symbol's location. + REL24 = 0x0006, + /// A 14-bit PC-relative offset to the symbol's location. + REL14 = 0x0007, + /// The 32-bit RVA of the target. + ADDR32NB = 0x000A, + /// The 32-bit offset of the target from the beginning of its section. + /// This is used to support debugging information and static thread local storage. + SECREL = 0x000B, + /// The 16-bit section index of the section that contains the target. + /// This is used to support debugging information. + SECTION = 0x000C, + /// The 16-bit offset of the target from the beginning of its section. + /// This is used to support debugging information and static thread local storage. + SECREL16 = 0x000F, + /// The high 16 bits of the target's 32-bit VA. + /// This is used for the first instruction in a two-instruction sequence that loads a full address. + /// This relocation must be immediately followed by a PAIR relocation whose SymbolTableIndex contains a signed 16-bit displacement that is added to the upper 16 bits that was taken from the location that is being relocated. + REFHI = 0x0010, + /// The low 16 bits of the target's VA. + REFLO = 0x0011, + /// A relocation that is valid only when it immediately follows a REFHI or SECRELHI relocation. + /// Its SymbolTableIndex contains a displacement and not an index into the symbol table. + PAIR = 0x0012, + /// The low 16 bits of the 32-bit offset of the target from the beginning of its section. + SECRELLO = 0x0013, + /// The 16-bit signed displacement of the target relative to the GP register. + GPREL = 0x0015, + /// The CLR token. + TOKEN = 0x0016, + _, + }; - /// The 19-bit offset to the relocation target, for conditional B instruction. - branch19 = 15, + /// Intel 386 Processors + /// The following relocation type indicators are defined for Intel 386 and compatible processors. + pub const I386 = enum(u16) { + /// The relocation is ignored. + ABSOLUTE = 0x0000, + /// Not supported. + DIR16 = 0x0001, + /// Not supported. + REL16 = 0x0002, + /// The target's 32-bit VA. + DIR32 = 0x0006, + /// The target's 32-bit RVA. + DIR32NB = 0x0007, + /// Not supported. + SEG12 = 0x0009, + /// The 16-bit section index of the section that contains the target. + /// This is used to support debugging information. + SECTION = 0x000A, + /// The 32-bit offset of the target from the beginning of its section. + /// This is used to support debugging information and static thread local storage. + SECREL = 0x000B, + /// The CLR token. + TOKEN = 0x000C, + /// A 7-bit offset from the base of the section that contains the target. + SECREL7 = 0x000D, + /// The 32-bit relative displacement to the target. + /// This supports the x86 relative branch and call instructions. + REL32 = 0x0014, + _, + }; - /// The 14-bit offset to the relocation target, for instructions TBZ and TBNZ. - branch14 = 16, + /// Intel Itanium Processor Family (IPF) + /// The following relocation type indicators are defined for the Intel Itanium processor family and compatible processors. + /// Note that relocations on instructions use the bundle's offset and slot number for the relocation offset. + pub const IA64 = enum(u16) { + /// The relocation is ignored. + ABSOLUTE = 0x0000, + /// The instruction relocation can be followed by an ADDEND relocation whose value is added to the target address before it is inserted into the specified slot in the IMM14 bundle. + /// The relocation target must be absolute or the image must be fixed. + IMM14 = 0x0001, + /// The instruction relocation can be followed by an ADDEND relocation whose value is added to the target address before it is inserted into the specified slot in the IMM22 bundle. + /// The relocation target must be absolute or the image must be fixed. + IMM22 = 0x0002, + /// The slot number of this relocation must be one (1). + /// The relocation can be followed by an ADDEND relocation whose value is added to the target address before it is stored in all three slots of the IMM64 bundle. + IMM64 = 0x0003, + /// The target's 32-bit VA. + /// This is supported only for /LARGEADDRESSAWARE:NO images. + DIR32 = 0x0004, + /// The target's 64-bit VA. + DIR64 = 0x0005, + /// The instruction is fixed up with the 25-bit relative displacement to the 16-bit aligned target. + /// The low 4 bits of the displacement are zero and are not stored. + PCREL21B = 0x0006, + /// The instruction is fixed up with the 25-bit relative displacement to the 16-bit aligned target. + /// The low 4 bits of the displacement, which are zero, are not stored. + PCREL21M = 0x0007, + /// The LSBs of this relocation's offset must contain the slot number whereas the rest is the bundle address. + /// The bundle is fixed up with the 25-bit relative displacement to the 16-bit aligned target. + /// The low 4 bits of the displacement are zero and are not stored. + PCREL21F = 0x0008, + /// The instruction relocation can be followed by an ADDEND relocation whose value is added to the target address and then a 22-bit GP-relative offset that is calculated and applied to the GPREL22 bundle. + GPREL22 = 0x0009, + /// The instruction is fixed up with the 22-bit GP-relative offset to the target symbol's literal table entry. + /// The linker creates this literal table entry based on this relocation and the ADDEND relocation that might follow. + LTOFF22 = 0x000A, + /// The 16-bit section index of the section contains the target. + /// This is used to support debugging information. + SECTION = 0x000B, + /// The instruction is fixed up with the 22-bit offset of the target from the beginning of its section. + /// This relocation can be followed immediately by an ADDEND relocation, whose Value field contains the 32-bit unsigned offset of the target from the beginning of the section. + SECREL22 = 0x000C, + /// The slot number for this relocation must be one (1). + /// The instruction is fixed up with the 64-bit offset of the target from the beginning of its section. + /// This relocation can be followed immediately by an ADDEND relocation whose Value field contains the 32-bit unsigned offset of the target from the beginning of the section. + SECREL64I = 0x000D, + /// The address of data to be fixed up with the 32-bit offset of the target from the beginning of its section. + SECREL32 = 0x000E, + /// The target's 32-bit RVA. + DIR32NB = 0x0010, + /// This is applied to a signed 14-bit immediate that contains the difference between two relocatable targets. + /// This is a declarative field for the linker that indicates that the compiler has already emitted this value. + SREL14 = 0x0011, + /// This is applied to a signed 22-bit immediate that contains the difference between two relocatable targets. + /// This is a declarative field for the linker that indicates that the compiler has already emitted this value. + SREL22 = 0x0012, + /// This is applied to a signed 32-bit immediate that contains the difference between two relocatable values. + /// This is a declarative field for the linker that indicates that the compiler has already emitted this value. + SREL32 = 0x0013, + /// This is applied to an unsigned 32-bit immediate that contains the difference between two relocatable values. + /// This is a declarative field for the linker that indicates that the compiler has already emitted this value. + UREL32 = 0x0014, + /// A 60-bit PC-relative fixup that always stays as a BRL instruction of an MLX bundle. + PCREL60X = 0x0015, + /// A 60-bit PC-relative fixup. + /// If the target displacement fits in a signed 25-bit field, convert the entire bundle to an MBB bundle with NOP.B in slot 1 and a 25-bit BR instruction (with the 4 lowest bits all zero and dropped) in slot 2. + PCREL60B = 0x0016, + /// A 60-bit PC-relative fixup. + /// If the target displacement fits in a signed 25-bit field, convert the entire bundle to an MFB bundle with NOP.F in slot 1 and a 25-bit (4 lowest bits all zero and dropped) BR instruction in slot 2. + PCREL60F = 0x0017, + /// A 60-bit PC-relative fixup. + /// If the target displacement fits in a signed 25-bit field, convert the entire bundle to an MIB bundle with NOP.I in slot 1 and a 25-bit (4 lowest bits all zero and dropped) BR instruction in slot 2. + PCREL60I = 0x0018, + /// A 60-bit PC-relative fixup. + /// If the target displacement fits in a signed 25-bit field, convert the entire bundle to an MMB bundle with NOP.M in slot 1 and a 25-bit (4 lowest bits all zero and dropped) BR instruction in slot 2. + PCREL60M = 0x0019, + /// A 64-bit GP-relative fixup. + IMMGPREL64 = 0x001a, + /// A CLR token. + TOKEN = 0x001b, + /// A 32-bit GP-relative fixup. + GPREL32 = 0x001c, + /// The relocation is valid only when it immediately follows one of the following relocations: IMM14, IMM22, IMM64, GPREL22, LTOFF22, LTOFF64, SECREL22, SECREL64I, or SECREL32. + /// Its value contains the addend to apply to instructions within a bundle, not for data. + ADDEND = 0x001F, + _, + }; - /// The 32-bit relative address from the byte following the relocation. - rel32 = 17, + /// MIPS Processors + /// The following relocation type indicators are defined for MIPS processors. + pub const MIPS = enum(u16) { + /// The relocation is ignored. + ABSOLUTE = 0x0000, + /// The high 16 bits of the target's 32-bit VA. + REFHALF = 0x0001, + /// The target's 32-bit VA. + REFWORD = 0x0002, + /// The low 26 bits of the target's VA. + /// This supports the MIPS J and JAL instructions. + JMPADDR = 0x0003, + /// The high 16 bits of the target's 32-bit VA. + /// This is used for the first instruction in a two-instruction sequence that loads a full address. + /// This relocation must be immediately followed by a PAIR relocation whose SymbolTableIndex contains a signed 16-bit displacement that is added to the upper 16 bits that are taken from the location that is being relocated. + REFHI = 0x0004, + /// The low 16 bits of the target's VA. + REFLO = 0x0005, + /// A 16-bit signed displacement of the target relative to the GP register. + GPREL = 0x0006, + /// The same as IMAGE_REL_MIPS_GPREL. + LITERAL = 0x0007, + /// The 16-bit section index of the section contains the target. + /// This is used to support debugging information. + SECTION = 0x000A, + /// The 32-bit offset of the target from the beginning of its section. + /// This is used to support debugging information and static thread local storage. + SECREL = 0x000B, + /// The low 16 bits of the 32-bit offset of the target from the beginning of its section. + SECRELLO = 0x000C, + /// The high 16 bits of the 32-bit offset of the target from the beginning of its section. + /// An IMAGE_REL_MIPS_PAIR relocation must immediately follow this one. + /// The SymbolTableIndex of the PAIR relocation contains a signed 16-bit displacement that is added to the upper 16 bits that are taken from the location that is being relocated. + SECRELHI = 0x000D, + /// The low 26 bits of the target's VA. + /// This supports the MIPS16 JAL instruction. + JMPADDR16 = 0x0010, + /// The target's 32-bit RVA. + REFWORDNB = 0x0022, + /// The relocation is valid only when it immediately follows a REFHI or SECRELHI relocation. + /// Its SymbolTableIndex contains a displacement and not an index into the symbol table. + PAIR = 0x0025, + _, + }; - _, + /// Mitsubishi M32R + /// The following relocation type indicators are defined for the Mitsubishi M32R processors. + pub const M32R = enum(u16) { + /// The relocation is ignored. + ABSOLUTE = 0x0000, + /// The target's 32-bit VA. + ADDR32 = 0x0001, + /// The target's 32-bit RVA. + ADDR32NB = 0x0002, + /// The target's 24-bit VA. + ADDR24 = 0x0003, + /// The target's 16-bit offset from the GP register. + GPREL16 = 0x0004, + /// The target's 24-bit offset from the program counter (PC), shifted left by 2 bits and sign-extended + PCREL24 = 0x0005, + /// The target's 16-bit offset from the PC, shifted left by 2 bits and sign-extended + PCREL16 = 0x0006, + /// The target's 8-bit offset from the PC, shifted left by 2 bits and sign-extended + PCREL8 = 0x0007, + /// The 16 MSBs of the target VA. + REFHALF = 0x0008, + /// The 16 MSBs of the target VA, adjusted for LSB sign extension. + /// This is used for the first instruction in a two-instruction sequence that loads a full 32-bit address. + /// This relocation must be immediately followed by a PAIR relocation whose SymbolTableIndex contains a signed 16-bit displacement that is added to the upper 16 bits that are taken from the location that is being relocated. + REFHI = 0x0009, + /// The 16 LSBs of the target VA. + REFLO = 0x000A, + /// The relocation must follow the REFHI relocation. + /// Its SymbolTableIndex contains a displacement and not an index into the symbol table. + PAIR = 0x000B, + /// The 16-bit section index of the section that contains the target. + /// This is used to support debugging information. + SECTION = 0x000C, + /// The 32-bit offset of the target from the beginning of its section. + /// This is used to support debugging information and static thread local storage. + SECREL = 0x000D, + /// The CLR token. + TOKEN = 0x000E, + _, + }; + }; }; diff --git a/lib/std/compress/flate.zig b/lib/std/compress/flate.zig index 4391a06787..20c3815a9e 100644 --- a/lib/std/compress/flate.zig +++ b/lib/std/compress/flate.zig @@ -1,8 +1,7 @@ const std = @import("../std.zig"); -/// When decompressing, the output buffer is used as the history window, so -/// less than this may result in failure to decompress streams that were -/// compressed with a larger window. +/// When compressing and decompressing, the provided buffer is used as the +/// history window, so it must be at least this size. pub const max_window_len = history_len * 2; pub const history_len = 32768; @@ -15,10 +14,6 @@ pub const Compress = @import("flate/Compress.zig"); /// produces the original full-size data. pub const Decompress = @import("flate/Decompress.zig"); -/// Compression without Lempel-Ziv match searching. Faster compression, less -/// memory requirements but bigger compressed sizes. -pub const HuffmanEncoder = @import("flate/HuffmanEncoder.zig"); - /// Container of the deflate bit stream body. Container adds header before /// deflate bit stream and footer after. It can bi gzip, zlib or raw (no header, /// no footer, raw bit stream). @@ -112,28 +107,24 @@ pub const Container = enum { switch (h.*) { .raw => {}, .gzip => |*gzip| { - gzip.update(buf); - gzip.count +%= buf.len; + gzip.crc.update(buf); + gzip.count +%= @truncate(buf.len); }, .zlib => |*zlib| { zlib.update(buf); }, - inline .gzip, .zlib => |*x| x.update(buf), } } pub fn writeFooter(hasher: *Hasher, writer: *std.Io.Writer) std.Io.Writer.Error!void { - var bits: [4]u8 = undefined; switch (hasher.*) { .gzip => |*gzip| { // GZIP 8 bytes footer // - 4 bytes, CRC32 (CRC-32) - // - 4 bytes, ISIZE (Input SIZE) - size of the original (uncompressed) input data modulo 2^32 - std.mem.writeInt(u32, &bits, gzip.final(), .little); - try writer.writeAll(&bits); - - std.mem.writeInt(u32, &bits, gzip.bytes_read, .little); - try writer.writeAll(&bits); + // - 4 bytes, ISIZE (Input SIZE) - size of the original + // (uncompressed) input data modulo 2^32 + try writer.writeInt(u32, gzip.crc.final(), .little); + try writer.writeInt(u32, gzip.count, .little); }, .zlib => |*zlib| { // ZLIB (RFC 1950) is big-endian, unlike GZIP (RFC 1952). @@ -141,8 +132,7 @@ pub const Container = enum { // Checksum value of the uncompressed data (excluding any // dictionary data) computed according to Adler-32 // algorithm. - std.mem.writeInt(u32, &bits, zlib.final, .big); - try writer.writeAll(&bits); + try writer.writeInt(u32, zlib.adler, .big); }, .raw => {}, } @@ -174,7 +164,6 @@ pub const Container = enum { }; test { - _ = HuffmanEncoder; _ = Compress; _ = Decompress; } diff --git a/lib/std/compress/flate/BlockWriter.zig b/lib/std/compress/flate/BlockWriter.zig deleted file mode 100644 index 47b1b298ce..0000000000 --- a/lib/std/compress/flate/BlockWriter.zig +++ /dev/null @@ -1,591 +0,0 @@ -//! Accepts list of tokens, decides what is best block type to write. What block -//! type will provide best compression. Writes header and body of the block. -const std = @import("std"); -const assert = std.debug.assert; -const Writer = std.Io.Writer; - -const BlockWriter = @This(); -const flate = @import("../flate.zig"); -const Compress = flate.Compress; -const HuffmanEncoder = flate.HuffmanEncoder; -const Token = @import("Token.zig"); - -const codegen_order = HuffmanEncoder.codegen_order; -const end_code_mark = 255; - -output: *Writer, - -codegen_freq: [HuffmanEncoder.codegen_code_count]u16, -literal_freq: [HuffmanEncoder.max_num_lit]u16, -distance_freq: [HuffmanEncoder.distance_code_count]u16, -codegen: [HuffmanEncoder.max_num_lit + HuffmanEncoder.distance_code_count + 1]u8, -literal_encoding: HuffmanEncoder, -distance_encoding: HuffmanEncoder, -codegen_encoding: HuffmanEncoder, -fixed_literal_encoding: HuffmanEncoder, -fixed_distance_encoding: HuffmanEncoder, -huff_distance: HuffmanEncoder, - -fixed_literal_codes: [HuffmanEncoder.max_num_frequencies]HuffmanEncoder.Code, -fixed_distance_codes: [HuffmanEncoder.distance_code_count]HuffmanEncoder.Code, -distance_codes: [HuffmanEncoder.distance_code_count]HuffmanEncoder.Code, - -pub fn init(output: *Writer) BlockWriter { - return .{ - .output = output, - .codegen_freq = undefined, - .literal_freq = undefined, - .distance_freq = undefined, - .codegen = undefined, - .literal_encoding = undefined, - .distance_encoding = undefined, - .codegen_encoding = undefined, - .fixed_literal_encoding = undefined, - .fixed_distance_encoding = undefined, - .huff_distance = undefined, - .fixed_literal_codes = undefined, - .fixed_distance_codes = undefined, - .distance_codes = undefined, - }; -} - -pub fn initBuffers(bw: *BlockWriter) void { - bw.fixed_literal_encoding = .fixedLiteralEncoder(&bw.fixed_literal_codes); - bw.fixed_distance_encoding = .fixedDistanceEncoder(&bw.fixed_distance_codes); - bw.huff_distance = .huffmanDistanceEncoder(&bw.distance_codes); -} - -/// Flush intrenal bit buffer to the writer. -/// Should be called only when bit stream is at byte boundary. -/// -/// That is after final block; when last byte could be incomplete or -/// after stored block; which is aligned to the byte boundary (it has x -/// padding bits after first 3 bits). -pub fn flush(self: *BlockWriter) Writer.Error!void { - try self.bit_writer.flush(); -} - -fn writeCode(self: *BlockWriter, c: Compress.HuffCode) Writer.Error!void { - try self.bit_writer.writeBits(c.code, c.len); -} - -/// RFC 1951 3.2.7 specifies a special run-length encoding for specifying -/// the literal and distance lengths arrays (which are concatenated into a single -/// array). This method generates that run-length encoding. -/// -/// The result is written into the codegen array, and the frequencies -/// of each code is written into the codegen_freq array. -/// Codes 0-15 are single byte codes. Codes 16-18 are followed by additional -/// information. Code bad_code is an end marker -/// -/// num_literals: The number of literals in literal_encoding -/// num_distances: The number of distances in distance_encoding -/// lit_enc: The literal encoder to use -/// dist_enc: The distance encoder to use -fn generateCodegen( - self: *BlockWriter, - num_literals: u32, - num_distances: u32, - lit_enc: *Compress.LiteralEncoder, - dist_enc: *Compress.DistanceEncoder, -) void { - for (self.codegen_freq, 0..) |_, i| { - self.codegen_freq[i] = 0; - } - - // Note that we are using codegen both as a temporary variable for holding - // a copy of the frequencies, and as the place where we put the result. - // This is fine because the output is always shorter than the input used - // so far. - var codegen = &self.codegen; // cache - // Copy the concatenated code sizes to codegen. Put a marker at the end. - var cgnl = codegen[0..num_literals]; - for (cgnl, 0..) |_, i| { - cgnl[i] = @as(u8, @intCast(lit_enc.codes[i].len)); - } - - cgnl = codegen[num_literals .. num_literals + num_distances]; - for (cgnl, 0..) |_, i| { - cgnl[i] = @as(u8, @intCast(dist_enc.codes[i].len)); - } - codegen[num_literals + num_distances] = end_code_mark; - - var size = codegen[0]; - var count: i32 = 1; - var out_index: u32 = 0; - var in_index: u32 = 1; - while (size != end_code_mark) : (in_index += 1) { - // INVARIANT: We have seen "count" copies of size that have not yet - // had output generated for them. - const next_size = codegen[in_index]; - if (next_size == size) { - count += 1; - continue; - } - // We need to generate codegen indicating "count" of size. - if (size != 0) { - codegen[out_index] = size; - out_index += 1; - self.codegen_freq[size] += 1; - count -= 1; - while (count >= 3) { - var n: i32 = 6; - if (n > count) { - n = count; - } - codegen[out_index] = 16; - out_index += 1; - codegen[out_index] = @as(u8, @intCast(n - 3)); - out_index += 1; - self.codegen_freq[16] += 1; - count -= n; - } - } else { - while (count >= 11) { - var n: i32 = 138; - if (n > count) { - n = count; - } - codegen[out_index] = 18; - out_index += 1; - codegen[out_index] = @as(u8, @intCast(n - 11)); - out_index += 1; - self.codegen_freq[18] += 1; - count -= n; - } - if (count >= 3) { - // 3 <= count <= 10 - codegen[out_index] = 17; - out_index += 1; - codegen[out_index] = @as(u8, @intCast(count - 3)); - out_index += 1; - self.codegen_freq[17] += 1; - count = 0; - } - } - count -= 1; - while (count >= 0) : (count -= 1) { - codegen[out_index] = size; - out_index += 1; - self.codegen_freq[size] += 1; - } - // Set up invariant for next time through the loop. - size = next_size; - count = 1; - } - // Marker indicating the end of the codegen. - codegen[out_index] = end_code_mark; -} - -const DynamicSize = struct { - size: u32, - num_codegens: u32, -}; - -/// dynamicSize returns the size of dynamically encoded data in bits. -fn dynamicSize( - self: *BlockWriter, - lit_enc: *Compress.LiteralEncoder, // literal encoder - dist_enc: *Compress.DistanceEncoder, // distance encoder - extra_bits: u32, -) DynamicSize { - var num_codegens = self.codegen_freq.len; - while (num_codegens > 4 and self.codegen_freq[codegen_order[num_codegens - 1]] == 0) { - num_codegens -= 1; - } - const header = 3 + 5 + 5 + 4 + (3 * num_codegens) + - self.codegen_encoding.bitLength(self.codegen_freq[0..]) + - self.codegen_freq[16] * 2 + - self.codegen_freq[17] * 3 + - self.codegen_freq[18] * 7; - const size = header + - lit_enc.bitLength(&self.literal_freq) + - dist_enc.bitLength(&self.distance_freq) + - extra_bits; - - return DynamicSize{ - .size = @as(u32, @intCast(size)), - .num_codegens = @as(u32, @intCast(num_codegens)), - }; -} - -/// fixedSize returns the size of dynamically encoded data in bits. -fn fixedSize(self: *BlockWriter, extra_bits: u32) u32 { - return 3 + - self.fixed_literal_encoding.bitLength(&self.literal_freq) + - self.fixed_distance_encoding.bitLength(&self.distance_freq) + - extra_bits; -} - -const StoredSize = struct { - size: u32, - storable: bool, -}; - -/// storedSizeFits calculates the stored size, including header. -/// The function returns the size in bits and whether the block -/// fits inside a single block. -fn storedSizeFits(in: ?[]const u8) StoredSize { - if (in == null) { - return .{ .size = 0, .storable = false }; - } - if (in.?.len <= HuffmanEncoder.max_store_block_size) { - return .{ .size = @as(u32, @intCast((in.?.len + 5) * 8)), .storable = true }; - } - return .{ .size = 0, .storable = false }; -} - -/// Write the header of a dynamic Huffman block to the output stream. -/// -/// num_literals: The number of literals specified in codegen -/// num_distances: The number of distances specified in codegen -/// num_codegens: The number of codegens used in codegen -/// eof: Is it the end-of-file? (end of stream) -fn dynamicHeader( - self: *BlockWriter, - num_literals: u32, - num_distances: u32, - num_codegens: u32, - eof: bool, -) Writer.Error!void { - const first_bits: u32 = if (eof) 5 else 4; - try self.bit_writer.writeBits(first_bits, 3); - try self.bit_writer.writeBits(num_literals - 257, 5); - try self.bit_writer.writeBits(num_distances - 1, 5); - try self.bit_writer.writeBits(num_codegens - 4, 4); - - var i: u32 = 0; - while (i < num_codegens) : (i += 1) { - const value = self.codegen_encoding.codes[codegen_order[i]].len; - try self.bit_writer.writeBits(value, 3); - } - - i = 0; - while (true) { - const code_word: u32 = @as(u32, @intCast(self.codegen[i])); - i += 1; - if (code_word == end_code_mark) { - break; - } - try self.writeCode(self.codegen_encoding.codes[@as(u32, @intCast(code_word))]); - - switch (code_word) { - 16 => { - try self.bit_writer.writeBits(self.codegen[i], 2); - i += 1; - }, - 17 => { - try self.bit_writer.writeBits(self.codegen[i], 3); - i += 1; - }, - 18 => { - try self.bit_writer.writeBits(self.codegen[i], 7); - i += 1; - }, - else => {}, - } - } -} - -fn storedHeader(self: *BlockWriter, length: usize, eof: bool) Writer.Error!void { - assert(length <= 65535); - const flag: u32 = if (eof) 1 else 0; - try self.bit_writer.writeBits(flag, 3); - try self.flush(); - const l: u16 = @intCast(length); - try self.bit_writer.writeBits(l, 16); - try self.bit_writer.writeBits(~l, 16); -} - -fn fixedHeader(self: *BlockWriter, eof: bool) Writer.Error!void { - // Indicate that we are a fixed Huffman block - var value: u32 = 2; - if (eof) { - value = 3; - } - try self.bit_writer.writeBits(value, 3); -} - -/// Write a block of tokens with the smallest encoding. Will choose block type. -/// The original input can be supplied, and if the huffman encoded data -/// is larger than the original bytes, the data will be written as a -/// stored block. -/// If the input is null, the tokens will always be Huffman encoded. -pub fn write(self: *BlockWriter, tokens: []const Token, eof: bool, input: ?[]const u8) Writer.Error!void { - const lit_and_dist = self.indexTokens(tokens); - const num_literals = lit_and_dist.num_literals; - const num_distances = lit_and_dist.num_distances; - - var extra_bits: u32 = 0; - const ret = storedSizeFits(input); - const stored_size = ret.size; - const storable = ret.storable; - - if (storable) { - // We only bother calculating the costs of the extra bits required by - // the length of distance fields (which will be the same for both fixed - // and dynamic encoding), if we need to compare those two encodings - // against stored encoding. - var length_code: u16 = Token.length_codes_start + 8; - while (length_code < num_literals) : (length_code += 1) { - // First eight length codes have extra size = 0. - extra_bits += @as(u32, @intCast(self.literal_freq[length_code])) * - @as(u32, @intCast(Token.lengthExtraBits(length_code))); - } - var distance_code: u16 = 4; - while (distance_code < num_distances) : (distance_code += 1) { - // First four distance codes have extra size = 0. - extra_bits += @as(u32, @intCast(self.distance_freq[distance_code])) * - @as(u32, @intCast(Token.distanceExtraBits(distance_code))); - } - } - - // Figure out smallest code. - // Fixed Huffman baseline. - var literal_encoding = &self.fixed_literal_encoding; - var distance_encoding = &self.fixed_distance_encoding; - var size = self.fixedSize(extra_bits); - - // Dynamic Huffman? - var num_codegens: u32 = 0; - - // Generate codegen and codegenFrequencies, which indicates how to encode - // the literal_encoding and the distance_encoding. - self.generateCodegen( - num_literals, - num_distances, - &self.literal_encoding, - &self.distance_encoding, - ); - self.codegen_encoding.generate(self.codegen_freq[0..], 7); - const dynamic_size = self.dynamicSize( - &self.literal_encoding, - &self.distance_encoding, - extra_bits, - ); - const dyn_size = dynamic_size.size; - num_codegens = dynamic_size.num_codegens; - - if (dyn_size < size) { - size = dyn_size; - literal_encoding = &self.literal_encoding; - distance_encoding = &self.distance_encoding; - } - - // Stored bytes? - if (storable and stored_size < size) { - try self.storedBlock(input.?, eof); - return; - } - - // Huffman. - if (@intFromPtr(literal_encoding) == @intFromPtr(&self.fixed_literal_encoding)) { - try self.fixedHeader(eof); - } else { - try self.dynamicHeader(num_literals, num_distances, num_codegens, eof); - } - - // Write the tokens. - try self.writeTokens(tokens, &literal_encoding.codes, &distance_encoding.codes); -} - -pub fn storedBlock(self: *BlockWriter, input: []const u8, eof: bool) Writer.Error!void { - try self.storedHeader(input.len, eof); - try self.bit_writer.writeBytes(input); -} - -/// writeBlockDynamic encodes a block using a dynamic Huffman table. -/// This should be used if the symbols used have a disproportionate -/// histogram distribution. -/// If input is supplied and the compression savings are below 1/16th of the -/// input size the block is stored. -fn dynamicBlock( - self: *BlockWriter, - tokens: []const Token, - eof: bool, - input: ?[]const u8, -) Writer.Error!void { - const total_tokens = self.indexTokens(tokens); - const num_literals = total_tokens.num_literals; - const num_distances = total_tokens.num_distances; - - // Generate codegen and codegenFrequencies, which indicates how to encode - // the literal_encoding and the distance_encoding. - self.generateCodegen( - num_literals, - num_distances, - &self.literal_encoding, - &self.distance_encoding, - ); - self.codegen_encoding.generate(self.codegen_freq[0..], 7); - const dynamic_size = self.dynamicSize(&self.literal_encoding, &self.distance_encoding, 0); - const size = dynamic_size.size; - const num_codegens = dynamic_size.num_codegens; - - // Store bytes, if we don't get a reasonable improvement. - - const stored_size = storedSizeFits(input); - const ssize = stored_size.size; - const storable = stored_size.storable; - if (storable and ssize < (size + (size >> 4))) { - try self.storedBlock(input.?, eof); - return; - } - - // Write Huffman table. - try self.dynamicHeader(num_literals, num_distances, num_codegens, eof); - - // Write the tokens. - try self.writeTokens(tokens, &self.literal_encoding.codes, &self.distance_encoding.codes); -} - -const TotalIndexedTokens = struct { - num_literals: u32, - num_distances: u32, -}; - -/// Indexes a slice of tokens followed by an end_block_marker, and updates -/// literal_freq and distance_freq, and generates literal_encoding -/// and distance_encoding. -/// The number of literal and distance tokens is returned. -fn indexTokens(self: *BlockWriter, tokens: []const Token) TotalIndexedTokens { - var num_literals: u32 = 0; - var num_distances: u32 = 0; - - for (self.literal_freq, 0..) |_, i| { - self.literal_freq[i] = 0; - } - for (self.distance_freq, 0..) |_, i| { - self.distance_freq[i] = 0; - } - - for (tokens) |t| { - if (t.kind == Token.Kind.literal) { - self.literal_freq[t.literal()] += 1; - continue; - } - self.literal_freq[t.lengthCode()] += 1; - self.distance_freq[t.distanceCode()] += 1; - } - // add end_block_marker token at the end - self.literal_freq[HuffmanEncoder.end_block_marker] += 1; - - // get the number of literals - num_literals = @as(u32, @intCast(self.literal_freq.len)); - while (self.literal_freq[num_literals - 1] == 0) { - num_literals -= 1; - } - // get the number of distances - num_distances = @as(u32, @intCast(self.distance_freq.len)); - while (num_distances > 0 and self.distance_freq[num_distances - 1] == 0) { - num_distances -= 1; - } - if (num_distances == 0) { - // We haven't found a single match. If we want to go with the dynamic encoding, - // we should count at least one distance to be sure that the distance huffman tree could be encoded. - self.distance_freq[0] = 1; - num_distances = 1; - } - self.literal_encoding.generate(&self.literal_freq, 15); - self.distance_encoding.generate(&self.distance_freq, 15); - return TotalIndexedTokens{ - .num_literals = num_literals, - .num_distances = num_distances, - }; -} - -/// Writes a slice of tokens to the output followed by and end_block_marker. -/// codes for literal and distance encoding must be supplied. -fn writeTokens( - self: *BlockWriter, - tokens: []const Token, - le_codes: []Compress.HuffCode, - oe_codes: []Compress.HuffCode, -) Writer.Error!void { - for (tokens) |t| { - if (t.kind == Token.Kind.literal) { - try self.writeCode(le_codes[t.literal()]); - continue; - } - - // Write the length - const le = t.lengthEncoding(); - try self.writeCode(le_codes[le.code]); - if (le.extra_bits > 0) { - try self.bit_writer.writeBits(le.extra_length, le.extra_bits); - } - - // Write the distance - const oe = t.distanceEncoding(); - try self.writeCode(oe_codes[oe.code]); - if (oe.extra_bits > 0) { - try self.bit_writer.writeBits(oe.extra_distance, oe.extra_bits); - } - } - // add end_block_marker at the end - try self.writeCode(le_codes[HuffmanEncoder.end_block_marker]); -} - -/// Encodes a block of bytes as either Huffman encoded literals or uncompressed bytes -/// if the results only gains very little from compression. -pub fn huffmanBlock(self: *BlockWriter, input: []const u8, eof: bool) Writer.Error!void { - // Add everything as literals - histogram(input, &self.literal_freq); - - self.literal_freq[HuffmanEncoder.end_block_marker] = 1; - - const num_literals = HuffmanEncoder.end_block_marker + 1; - self.distance_freq[0] = 1; - const num_distances = 1; - - self.literal_encoding.generate(&self.literal_freq, 15); - - // Figure out smallest code. - // Always use dynamic Huffman or Store - var num_codegens: u32 = 0; - - // Generate codegen and codegenFrequencies, which indicates how to encode - // the literal_encoding and the distance_encoding. - self.generateCodegen( - num_literals, - num_distances, - &self.literal_encoding, - &self.huff_distance, - ); - self.codegen_encoding.generate(self.codegen_freq[0..], 7); - const dynamic_size = self.dynamicSize(&self.literal_encoding, &self.huff_distance, 0); - const size = dynamic_size.size; - num_codegens = dynamic_size.num_codegens; - - // Store bytes, if we don't get a reasonable improvement. - const stored_size_ret = storedSizeFits(input); - const ssize = stored_size_ret.size; - const storable = stored_size_ret.storable; - - if (storable and ssize < (size + (size >> 4))) { - try self.storedBlock(input, eof); - return; - } - - // Huffman. - try self.dynamicHeader(num_literals, num_distances, num_codegens, eof); - const encoding = self.literal_encoding.codes[0..257]; - - for (input) |t| { - const c = encoding[t]; - try self.bit_writer.writeBits(c.code, c.len); - } - try self.writeCode(encoding[HuffmanEncoder.end_block_marker]); -} - -fn histogram(b: []const u8, h: *[286]u16) void { - // Clear histogram - for (h, 0..) |_, i| { - h[i] = 0; - } - - var lh = h.*[0..256]; - for (b) |t| { - lh[t] += 1; - } -} diff --git a/lib/std/compress/flate/Compress.zig b/lib/std/compress/flate/Compress.zig index 2249ece4c0..d97053befd 100644 --- a/lib/std/compress/flate/Compress.zig +++ b/lib/std/compress/flate/Compress.zig @@ -1,332 +1,2550 @@ -//! Default compression algorithm. Has two steps: tokenization and token -//! encoding. +//! Allocates statically ~224K (128K lookup, 96K tokens). //! -//! Tokenization takes uncompressed input stream and produces list of tokens. -//! Each token can be literal (byte of data) or match (backrefernce to previous -//! data with length and distance). Tokenization accumulators 32K tokens, when -//! full or `flush` is called tokens are passed to the `block_writer`. Level -//! defines how hard (how slow) it tries to find match. -//! -//! Block writer will decide which type of deflate block to write (stored, fixed, -//! dynamic) and encode tokens to the output byte stream. Client has to call -//! `finish` to write block with the final bit set. -//! -//! Container defines type of header and footer which can be gzip, zlib or raw. -//! They all share same deflate body. Raw has no header or footer just deflate -//! body. -//! -//! Compression algorithm explained in rfc-1951 (slightly edited for this case): -//! -//! The compressor uses a chained hash table `lookup` to find duplicated -//! strings, using a hash function that operates on 4-byte sequences. At any -//! given point during compression, let XYZW be the next 4 input bytes -//! (lookahead) to be examined (not necessarily all different, of course). -//! First, the compressor examines the hash chain for XYZW. If the chain is -//! empty, the compressor simply writes out X as a literal byte and advances -//! one byte in the input. If the hash chain is not empty, indicating that the -//! sequence XYZW (or, if we are unlucky, some other 4 bytes with the same -//! hash function value) has occurred recently, the compressor compares all -//! strings on the XYZW hash chain with the actual input data sequence -//! starting at the current point, and selects the longest match. -//! -//! To improve overall compression, the compressor defers the selection of -//! matches ("lazy matching"): after a match of length N has been found, the -//! compressor searches for a longer match starting at the next input byte. If -//! it finds a longer match, it truncates the previous match to a length of -//! one (thus producing a single literal byte) and then emits the longer -//! match. Otherwise, it emits the original match, and, as described above, -//! advances N bytes before continuing. -//! -//! -//! Allocates statically ~400K (192K lookup, 128K tokens, 64K window). +//! The source of an `error.WriteFailed` is always the backing writer. After an +//! `error.WriteFailed`, the `.writer` becomes `.failing` and is unrecoverable. +//! After a `flush`, the writer also becomes `.failing` since the stream has +//! been finished. This behavior also applies to `Raw` and `Huffman`. + +// Implementation details: +// A chained hash table is used to find matches. `drain` always preserves `flate.history_len` +// bytes to use as a history and avoids tokenizing the final bytes since they can be part of +// a longer match with unwritten bytes (unless it is a `flush`). The minimum match searched +// for is of length `seq_bytes`. If a match is made, a longer match is also checked for at +// the next byte (lazy matching) if the last match does not meet the `Options.lazy` threshold. +// +// Up to `block_token` tokens are accumalated in `buffered_tokens` and are outputted in +// `write_block` which determines the optimal block type and frequencies. const builtin = @import("builtin"); const std = @import("std"); -const assert = std.debug.assert; -const testing = std.testing; -const expect = testing.expect; const mem = std.mem; const math = std.math; -const Writer = std.Io.Writer; +const assert = std.debug.assert; +const Io = std.Io; +const Writer = Io.Writer; const Compress = @This(); -const Token = @import("Token.zig"); -const BlockWriter = @import("BlockWriter.zig"); +const token = @import("token.zig"); const flate = @import("../flate.zig"); -const Container = flate.Container; -const Lookup = @import("Lookup.zig"); -const HuffmanEncoder = flate.HuffmanEncoder; -const LiteralNode = HuffmanEncoder.LiteralNode; - -lookup: Lookup = .{}, -tokens: Tokens = .{}, -block_writer: BlockWriter, -level: LevelArgs, -hasher: Container.Hasher, -writer: Writer, -state: State, -// Match and literal at the previous position. -// Used for lazy match finding in processWindow. -prev_match: ?Token = null, -prev_literal: ?u8 = null, +/// Until #104 is implemented, a ?u15 takes 4 bytes, which is unacceptable +/// as it doubles the size of this already massive structure. +/// +/// Also, there are no `to` / `from` methods because LLVM 21 does not +/// optimize away the conversion from and to `?u15`. +const PackedOptionalU15 = packed struct(u16) { + value: u15, + is_null: bool, -pub const State = enum { header, middle, ended }; + pub fn int(p: PackedOptionalU15) u16 { + return @bitCast(p); + } -/// Trades between speed and compression size. -/// Starts with level 4: in [zlib](https://github.com/madler/zlib/blob/abd3d1a28930f89375d4b41408b39f6c1be157b2/deflate.c#L115C1-L117C43) -/// levels 1-3 are using different algorithm to perform faster but with less -/// compression. That is not implemented here. -pub const Level = enum(u4) { - level_4 = 4, - level_5 = 5, - level_6 = 6, - level_7 = 7, - level_8 = 8, - level_9 = 9, - - fast = 0xb, - default = 0xc, - best = 0xd, + pub const null_bit: PackedOptionalU15 = .{ .value = 0, .is_null = true }; }; -/// Number of tokens to accumulate in deflate before starting block encoding. -/// -/// In zlib this depends on memlevel: 6 + memlevel, where default memlevel is -/// 8 and max 9 that gives 14 or 15 bits. -pub const n_tokens = 1 << 15; - -/// Algorithm knobs for each level. -const LevelArgs = struct { - good: u16, // Do less lookups if we already have match of this length. - nice: u16, // Stop looking for better match if we found match with at least this length. - lazy: u16, // Don't do lazy match find if got match with at least this length. - chain: u16, // How many lookups for previous match to perform. - - pub fn get(level: Level) LevelArgs { - return switch (level) { - .fast, .level_4 => .{ .good = 4, .lazy = 4, .nice = 16, .chain = 16 }, - .level_5 => .{ .good = 8, .lazy = 16, .nice = 32, .chain = 32 }, - .default, .level_6 => .{ .good = 8, .lazy = 16, .nice = 128, .chain = 128 }, - .level_7 => .{ .good = 8, .lazy = 32, .nice = 128, .chain = 256 }, - .level_8 => .{ .good = 32, .lazy = 128, .nice = 258, .chain = 1024 }, - .best, .level_9 => .{ .good = 32, .lazy = 258, .nice = 258, .chain = 4096 }, +/// After `flush` is called, all vtable calls with result in `error.WriteFailed.` +writer: Writer, +has_history: bool, +bit_writer: BitWriter, +buffered_tokens: struct { + /// List of `TokenBufferEntryHeader`s and their trailing data. + list: [@as(usize, block_tokens) * 3]u8, + pos: u32, + n: u16, + lit_freqs: [286]u16, + dist_freqs: [30]u16, + + pub const empty: @This() = .{ + .list = undefined, + .pos = 0, + .n = 0, + .lit_freqs = @splat(0), + .dist_freqs = @splat(0), + }; +}, +lookup: struct { + /// Indexes are the hashes of four-bytes sequences. + /// + /// Values are the positions in `chain` of the previous four bytes with the same hash. + head: [1 << lookup_hash_bits]PackedOptionalU15, + /// Values are the non-zero number of bytes backwards in the history with the same hash. + /// + /// The relationship of chain indexes and bytes relative to the latest history byte is + /// `chain_pos -% chain_index = history_index`. + chain: [32768]PackedOptionalU15, + /// The index in `chain` which is of the newest byte of the history. + chain_pos: u15, +}, +container: flate.Container, +hasher: flate.Container.Hasher, +opts: Options, + +const BitWriter = struct { + output: *Writer, + buffered: u7, + buffered_n: u3, + + pub fn init(w: *Writer) BitWriter { + return .{ + .output = w, + .buffered = 0, + .buffered_n = 0, }; } + + /// Asserts `bits` is zero-extended + pub fn write(b: *BitWriter, bits: u56, n: u6) Writer.Error!void { + assert(@as(u8, b.buffered) >> b.buffered_n == 0); + assert(@as(u57, bits) >> n == 0); // n may be 56 so u57 is needed + const combined = @shlExact(@as(u64, bits), b.buffered_n) | b.buffered; + const combined_bits = @as(u6, b.buffered_n) + n; + + const out = try b.output.writableSliceGreedy(8); + mem.writeInt(u64, out[0..8], combined, .little); + b.output.advance(combined_bits / 8); + + b.buffered_n = @truncate(combined_bits); + b.buffered = @intCast(combined >> (combined_bits - b.buffered_n)); + } + + /// Assserts one byte can be written to `b.otuput` without rebasing. + pub fn byteAlign(b: *BitWriter) void { + b.output.unusedCapacitySlice()[0] = b.buffered; + b.output.advance(@intFromBool(b.buffered_n != 0)); + b.buffered = 0; + b.buffered_n = 0; + } + + pub fn writeClen( + b: *BitWriter, + hclen: u4, + clen_values: []u8, + clen_extra: []u8, + clen_codes: [19]u16, + clen_bits: [19]u4, + ) Writer.Error!void { + // Write the first four clen entries seperately since they are always present, + // and writing them all at once takes too many bits. + try b.write(clen_bits[token.codegen_order[0]] | + @shlExact(@as(u6, clen_bits[token.codegen_order[1]]), 3) | + @shlExact(@as(u9, clen_bits[token.codegen_order[2]]), 6) | + @shlExact(@as(u12, clen_bits[token.codegen_order[3]]), 9), 12); + + var i = hclen; + var clen_bits_table: u45 = 0; + while (i != 0) { + i -= 1; + clen_bits_table <<= 3; + clen_bits_table |= clen_bits[token.codegen_order[4..][i]]; + } + try b.write(clen_bits_table, @as(u6, hclen) * 3); + + for (clen_values, clen_extra) |value, extra| { + try b.write( + clen_codes[value] | @shlExact(@as(u16, extra), clen_bits[value]), + clen_bits[value] + @as(u3, switch (value) { + 0...15 => 0, + 16 => 2, + 17 => 3, + 18 => 7, + else => unreachable, + }), + ); + } + } +}; + +/// Number of tokens to accumulate before outputing as a block. +/// The maximum value is `math.maxInt(u16) - 1` since one token is reserved for end-of-block. +const block_tokens: u16 = 1 << 15; +const lookup_hash_bits = 15; +const Hash = u16; // `u[lookup_hash_bits]` is not used due to worse optimization (with LLVM 21) +const seq_bytes = 3; // not intended to be changed +const Seq = std.meta.Int(.unsigned, seq_bytes * 8); + +const TokenBufferEntryHeader = packed struct(u16) { + kind: enum(u1) { + /// Followed by non-zero `data` byte literals. + bytes, + /// Followed by the length as a byte + match, + }, + data: u15, +}; + +const BlockHeader = packed struct(u3) { + final: bool, + kind: enum(u2) { stored, fixed, dynamic, _ }, + + pub fn int(h: BlockHeader) u3 { + return @bitCast(h); + } + + pub const Dynamic = packed struct(u17) { + regular: BlockHeader, + hlit: u5, + hdist: u5, + hclen: u4, + + pub fn int(h: Dynamic) u17 { + return @bitCast(h); + } + }; }; +fn outputMatch(c: *Compress, dist: u15, len: u8) Writer.Error!void { + // This must come first. Instead of ensuring a full block is never left buffered, + // draining it is defered to allow end of stream to be indicated. + if (c.buffered_tokens.n == block_tokens) { + @branchHint(.unlikely); // LLVM 21 optimizes this branch as the more likely without + try c.writeBlock(false); + } + const header: TokenBufferEntryHeader = .{ .kind = .match, .data = dist }; + c.buffered_tokens.list[c.buffered_tokens.pos..][0..2].* = @bitCast(header); + c.buffered_tokens.list[c.buffered_tokens.pos + 2] = len; + c.buffered_tokens.pos += 3; + c.buffered_tokens.n += 1; + + c.buffered_tokens.lit_freqs[@as(usize, 257) + token.LenCode.fromVal(len).toInt()] += 1; + c.buffered_tokens.dist_freqs[token.DistCode.fromVal(dist).toInt()] += 1; +} + +fn outputBytes(c: *Compress, bytes: []const u8) Writer.Error!void { + var remaining = bytes; + while (remaining.len != 0) { + if (c.buffered_tokens.n == block_tokens) { + @branchHint(.unlikely); // LLVM 21 optimizes this branch as the more likely without + try c.writeBlock(false); + } + + const n = @min(remaining.len, block_tokens - c.buffered_tokens.n, math.maxInt(u15)); + assert(n != 0); + const header: TokenBufferEntryHeader = .{ .kind = .bytes, .data = n }; + c.buffered_tokens.list[c.buffered_tokens.pos..][0..2].* = @bitCast(header); + @memcpy(c.buffered_tokens.list[c.buffered_tokens.pos + 2 ..][0..n], remaining[0..n]); + c.buffered_tokens.pos += @as(u32, 2) + n; + c.buffered_tokens.n += n; + + for (remaining[0..n]) |b| { + c.buffered_tokens.lit_freqs[b] += 1; + } + remaining = remaining[n..]; + } +} + +fn hash(x: u32) Hash { + return @intCast((x *% 0x9E3779B1) >> (32 - lookup_hash_bits)); +} + +/// Trades between speed and compression size. +/// +/// Default paramaters are [taken from zlib] +/// (https://github.com/madler/zlib/blob/v1.3.1/deflate.c#L112) pub const Options = struct { - level: Level = .default, - container: Container = .raw, + /// Perform less lookups when a match of at least this length has been found. + good: u16, + /// Stop when a match of at least this length has been found. + nice: u16, + /// Don't attempt a lazy match find when a match of at least this length has been found. + lazy: u16, + /// Check this many previous locations with the same hash for longer matches. + chain: u16, + + // zig fmt: off + pub const level_1: Options = .{ .good = 4, .nice = 8, .lazy = 0, .chain = 4 }; + pub const level_2: Options = .{ .good = 4, .nice = 16, .lazy = 0, .chain = 8 }; + pub const level_3: Options = .{ .good = 4, .nice = 32, .lazy = 0, .chain = 32 }; + pub const level_4: Options = .{ .good = 4, .nice = 16, .lazy = 4, .chain = 16 }; + pub const level_5: Options = .{ .good = 8, .nice = 32, .lazy = 16, .chain = 32 }; + pub const level_6: Options = .{ .good = 8, .nice = 128, .lazy = 16, .chain = 128 }; + pub const level_7: Options = .{ .good = 8, .nice = 128, .lazy = 32, .chain = 256 }; + pub const level_8: Options = .{ .good = 32, .nice = 258, .lazy = 128, .chain = 1024 }; + pub const level_9: Options = .{ .good = 32, .nice = 258, .lazy = 258, .chain = 4096 }; + // zig fmt: on + pub const fastest = level_1; + pub const default = level_6; + pub const best = level_9; }; -pub fn init(output: *Writer, buffer: []u8, options: Options) Compress { +/// It is asserted `buffer` is least `flate.max_history_len` bytes. +/// It is asserted `output` has a capacity of at least 8 bytes. +pub fn init( + output: *Writer, + buffer: []u8, + container: flate.Container, + opts: Options, +) Writer.Error!Compress { + assert(output.buffer.len > 8); + assert(buffer.len >= flate.max_window_len); + + // note that disallowing some of these simplifies matching logic + assert(opts.chain != 0); // use `Huffman`, disallowing this simplies matching + assert(opts.good >= 3 and opts.nice >= 3); // a match will (usually) not be found + assert(opts.good <= 258 and opts.nice <= 258); // a longer match will not be found + assert(opts.lazy <= opts.nice); // a longer match will (usually) not be found + if (opts.good <= opts.lazy) assert(opts.chain >= 1 << 2); // chain can be reduced to zero + + try output.writeAll(container.header()); return .{ - .block_writer = .init(output), - .level = .get(options.level), - .hasher = .init(options.container), - .state = .header, .writer = .{ .buffer = buffer, - .vtable = &.{ .drain = drain }, + .vtable = &.{ + .drain = drain, + .flush = flush, + .rebase = rebase, + }, + }, + .has_history = false, + .bit_writer = .init(output), + .buffered_tokens = .empty, + .lookup = .{ + // init `value` is max so there is 0xff pattern + .head = @splat(.{ .value = math.maxInt(u15), .is_null = true }), + .chain = undefined, + .chain_pos = math.maxInt(u15), }, + .container = container, + .opts = opts, + .hasher = .init(container), }; } -// Tokens store -const Tokens = struct { - list: [n_tokens]Token = undefined, - pos: usize = 0, +fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize { + errdefer w.* = .failing; + // There may have not been enough space in the buffer and the write was sent directly here. + // However, it is required that all data goes through the buffer to keep a history. + // + // Additionally, ensuring the buffer is always full ensures there is always a full history + // after. + const data_n = w.buffer.len - w.end; + _ = w.fixedDrain(data, splat) catch {}; + assert(w.end == w.buffer.len); + try rebaseInner(w, 0, 1, false); + return data_n; +} + +fn flush(w: *Writer) Writer.Error!void { + defer w.* = .failing; + const c: *Compress = @fieldParentPtr("writer", w); + try rebaseInner(w, 0, w.buffer.len - flate.history_len, true); + try c.bit_writer.output.rebase(0, 1); + c.bit_writer.byteAlign(); + try c.hasher.writeFooter(c.bit_writer.output); +} + +fn rebase(w: *Writer, preserve: usize, capacity: usize) Writer.Error!void { + return rebaseInner(w, preserve, capacity, false); +} + +pub const rebase_min_preserve = flate.history_len; +pub const rebase_reserved_capacity = (token.max_length + 1) + seq_bytes; + +fn rebaseInner(w: *Writer, preserve: usize, capacity: usize, eos: bool) Writer.Error!void { + if (!eos) { + assert(@max(preserve, rebase_min_preserve) + (capacity + rebase_reserved_capacity) <= w.buffer.len); + assert(w.end >= flate.history_len + rebase_reserved_capacity); // Above assert should + // fail since rebase is only called when `capacity` is not present. This assertion is + // important because a full history is required at the end. + } else { + assert(preserve == 0 and capacity == w.buffer.len - flate.history_len); + } + + const c: *Compress = @fieldParentPtr("writer", w); + const buffered = w.buffered(); + + const start = @as(usize, flate.history_len) * @intFromBool(c.has_history); + const lit_end: usize = if (!eos) + buffered.len - rebase_reserved_capacity - (preserve -| flate.history_len) + else + buffered.len -| (seq_bytes - 1); + + var i = start; + var last_unmatched = i; + // Read from `w.buffer` instead of `buffered` since the latter may not + // have enough bytes. If this is the case, this variable is not used. + var seq: Seq = mem.readInt( + std.meta.Int(.unsigned, (seq_bytes - 1) * 8), + w.buffer[i..][0 .. seq_bytes - 1], + .big, + ); + if (buffered[i..].len < seq_bytes - 1) { + @branchHint(.unlikely); + assert(eos); + seq = undefined; + assert(i >= lit_end); + } + + while (i < lit_end) { + var match_start = i; + seq <<= 8; + seq |= buffered[i + (seq_bytes - 1)]; + var match = c.matchAndAddHash(i, hash(seq), token.min_length - 1, c.opts.chain, c.opts.good); + i += 1; + if (match.len < token.min_length) continue; + + var match_unadded = match.len - 1; + lazy: { + if (match.len >= c.opts.lazy) break :lazy; + if (match.len >= c.writer.buffered()[i..].len) { + @branchHint(.unlikely); // Only end of stream + break :lazy; + } - fn add(self: *Tokens, t: Token) void { - self.list[self.pos] = t; - self.pos += 1; + var chain = c.opts.chain; + var good = c.opts.good; + if (match.len >= good) { + chain >>= 2; + good = math.maxInt(u8); // Reduce only once + } + + seq <<= 8; + seq |= buffered[i + (seq_bytes - 1)]; + const lazy = c.matchAndAddHash(i, hash(seq), match.len, chain, good); + match_unadded -= 1; + i += 1; + + if (lazy.len > match.len) { + match_start += 1; + match = lazy; + match_unadded = match.len - 1; + } + } + + assert(i + match_unadded == match_start + match.len); + assert(mem.eql( + u8, + buffered[match_start..][0..match.len], + buffered[match_start - 1 - match.dist ..][0..match.len], + )); // This assert also seems to help codegen. + + try c.outputBytes(buffered[last_unmatched..match_start]); + try c.outputMatch(@intCast(match.dist), @intCast(match.len - 3)); + + last_unmatched = match_start + match.len; + if (last_unmatched + seq_bytes >= w.end) { + @branchHint(.unlikely); + assert(eos); + i = undefined; + break; + } + + while (true) { + seq <<= 8; + seq |= buffered[i + (seq_bytes - 1)]; + _ = c.addHash(i, hash(seq)); + i += 1; + + match_unadded -= 1; + if (match_unadded == 0) break; + } + assert(i == match_start + match.len); } - fn full(self: *Tokens) bool { - return self.pos == self.list.len; + if (eos) { + i = undefined; // (from match hashing logic) + try c.outputBytes(buffered[last_unmatched..]); + c.hasher.update(buffered[start..]); + try c.writeBlock(true); + return; } - fn reset(self: *Tokens) void { - self.pos = 0; + try c.outputBytes(buffered[last_unmatched..i]); + c.hasher.update(buffered[start..i]); + + const preserved = buffered[i - flate.history_len ..]; + assert(preserved.len > @max(rebase_min_preserve, preserve)); + @memmove(w.buffer[0..preserved.len], preserved); + w.end = preserved.len; + c.has_history = true; +} + +fn addHash(c: *Compress, i: usize, h: Hash) void { + assert(h == hash(mem.readInt(Seq, c.writer.buffer[i..][0..seq_bytes], .big))); + + const l = &c.lookup; + l.chain_pos +%= 1; + + // Equivilent to the below, however LLVM 21 does not optimize `@subWithOverflow` well at all. + // const replaced_i, const no_replace = @subWithOverflow(i, flate.history_len); + // if (no_replace == 0) { + if (i >= flate.history_len) { + @branchHint(.likely); + const replaced_i = i - flate.history_len; + // The following is the same as the below except uses a 32-bit load to help optimizations + // const replaced_seq = mem.readInt(Seq, c.writer.buffer[replaced_i..][0..seq_bytes], .big); + comptime assert(@sizeOf(Seq) <= @sizeOf(u32)); + const replaced_u32 = mem.readInt(u32, c.writer.buffered()[replaced_i..][0..4], .big); + const replaced_seq: Seq = @intCast(replaced_u32 >> (32 - @bitSizeOf(Seq))); + + const replaced_h = hash(replaced_seq); + // The following is equivilent to the below since LLVM 21 doesn't optimize it well. + // l.head[replaced_h].is_null = l.head[replaced_h].is_null or + // l.head[replaced_h].int() == l.chain_pos; + const empty_head = l.head[replaced_h].int() == l.chain_pos; + const null_flag = PackedOptionalU15.int(.{ .is_null = empty_head, .value = 0 }); + l.head[replaced_h] = @bitCast(l.head[replaced_h].int() | null_flag); } - fn tokens(self: *Tokens) []const Token { - return self.list[0..self.pos]; + const prev_chain_index = l.head[h]; + l.chain[l.chain_pos] = @bitCast((l.chain_pos -% prev_chain_index.value) | + (prev_chain_index.int() & PackedOptionalU15.null_bit.int())); // Preserves null + l.head[h] = .{ .value = l.chain_pos, .is_null = false }; +} + +/// If the match is shorter, the returned value can be any value `<= old`. +fn betterMatchLen(old: u16, prev: []const u8, bytes: []const u8) u16 { + assert(old < @min(bytes.len, token.max_length)); + assert(prev.len >= bytes.len); + assert(bytes.len >= token.min_length); + + var i: u16 = 0; + const Block = std.meta.Int(.unsigned, @min(math.divCeil( + comptime_int, + math.ceilPowerOfTwoAssert(usize, @bitSizeOf(usize)), + 8, + ) catch unreachable, 256) * 8); + + if (bytes.len < token.max_length) { + @branchHint(.unlikely); // Only end of stream + + while (bytes[i..].len >= @sizeOf(Block)) { + const a = mem.readInt(Block, prev[i..][0..@sizeOf(Block)], .little); + const b = mem.readInt(Block, bytes[i..][0..@sizeOf(Block)], .little); + const diff = a ^ b; + if (diff != 0) { + @branchHint(.likely); + i += @ctz(diff) / 8; + return i; + } + i += @sizeOf(Block); + } + + while (i != bytes.len and prev[i] == bytes[i]) { + i += 1; + } + assert(i < token.max_length); + return i; } -}; -fn drain(me: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize { - _ = data; - _ = splat; - const c: *Compress = @fieldParentPtr("writer", me); - const out = c.block_writer.output; - switch (c.state) { - .header => { - c.state = .middle; - const header = c.hasher.container().header(); - try out.writeAll(header); - return header.len; - }, - .middle => {}, - .ended => unreachable, + if (old >= @sizeOf(Block)) { + // Check that a longer end is present, otherwise the match is always worse + const a = mem.readInt(Block, prev[old + 1 - @sizeOf(Block) ..][0..@sizeOf(Block)], .little); + const b = mem.readInt(Block, bytes[old + 1 - @sizeOf(Block) ..][0..@sizeOf(Block)], .little); + if (a != b) return i; + } + + while (true) { + const a = mem.readInt(Block, prev[i..][0..@sizeOf(Block)], .little); + const b = mem.readInt(Block, bytes[i..][0..@sizeOf(Block)], .little); + const diff = a ^ b; + if (diff != 0) { + i += @ctz(diff) / 8; + return i; + } + i += @sizeOf(Block); + if (i == 256) break; + } + + const a = mem.readInt(u16, prev[i..][0..2], .little); + const b = mem.readInt(u16, bytes[i..][0..2], .little); + const diff = a ^ b; + i += @ctz(diff) / 8; + assert(i <= token.max_length); + return i; +} + +test betterMatchLen { + try std.testing.fuzz({}, testFuzzedMatchLen, .{}); +} + +fn testFuzzedMatchLen(_: void, input: []const u8) !void { + @disableInstrumentation(); + var r: Io.Reader = .fixed(input); + var buf: [1024]u8 = undefined; + var w: Writer = .fixed(&buf); + var old = r.takeLeb128(u9) catch 0; + var bytes_off = @max(1, r.takeLeb128(u10) catch 258); + const prev_back = @max(1, r.takeLeb128(u10) catch 258); + + while (r.takeByte()) |byte| { + const op: packed struct(u8) { + kind: enum(u2) { splat, copy, insert_imm, insert }, + imm: u6, + + pub fn immOrByte(op_s: @This(), r_s: *Io.Reader) usize { + return if (op_s.imm == 0) op_s.imm else @as(usize, r_s.takeByte() catch 0) + 64; + } + } = @bitCast(byte); + (switch (op.kind) { + .splat => w.splatByteAll(r.takeByte() catch 0, op.immOrByte(&r)), + .copy => write: { + const start = w.buffered().len -| op.immOrByte(&r); + const len = @min(w.buffered().len - start, r.takeByte() catch 3); + break :write w.writeAll(w.buffered()[start..][0..len]); + }, + .insert_imm => w.writeByte(op.imm), + .insert => w.writeAll(r.take( + @min(r.bufferedLen(), @as(usize, op.imm) + 1), + ) catch unreachable), + }) catch break; + } else |_| {} + + w.splatByteAll(0, (1 + 3) -| w.buffered().len) catch unreachable; + bytes_off = @min(bytes_off, @as(u10, @intCast(w.buffered().len - 3))); + const prev_off = bytes_off -| prev_back; + assert(prev_off < bytes_off); + const prev = w.buffered()[prev_off..]; + const bytes = w.buffered()[bytes_off..]; + old = @min(old, bytes.len - 1, token.max_length - 1); + + const diff_index = mem.indexOfDiff(u8, prev, bytes).?; // unwrap since lengths are not same + const expected_len = @min(diff_index, 258); + errdefer std.debug.print( + \\prev : '{any}' + \\bytes: '{any}' + \\old : {} + \\expected: {?} + \\actual : {} + ++ "\n", .{ + prev, bytes, old, + if (old < expected_len) expected_len else null, betterMatchLen(old, prev, bytes), + }); + if (old < expected_len) { + try std.testing.expectEqual(expected_len, betterMatchLen(old, prev, bytes)); + } else { + try std.testing.expect(betterMatchLen(old, prev, bytes) <= old); + } +} + +fn matchAndAddHash(c: *Compress, i: usize, h: Hash, gt: u16, max_chain: u16, good_: u16) struct { + dist: u16, + len: u16, +} { + const l = &c.lookup; + const buffered = c.writer.buffered(); + + var chain_limit = max_chain; + var best_dist: u16 = undefined; + var best_len = gt; + const nice = @min(c.opts.nice, buffered[i..].len); + var good = good_; + + search: { + if (l.head[h].is_null) break :search; + // Actually a u15, but LLVM 21 does not optimize that as well (it truncates it each use). + var dist: u16 = l.chain_pos -% l.head[h].value; + while (true) { + chain_limit -= 1; + + const match_len = betterMatchLen(best_len, buffered[i - 1 - dist ..], buffered[i..]); + if (match_len > best_len) { + best_dist = dist; + best_len = match_len; + if (best_len >= nice) break; + if (best_len >= good) { + chain_limit >>= 2; + good = math.maxInt(u8); // Reduce only once + } + } + + if (chain_limit == 0) break; + const next_chain_index = l.chain_pos -% @as(u15, @intCast(dist)); + // Equivilent to the below, however LLVM 21 optimizes the below worse. + // if (l.chain[next_chain_index].is_null) break; + // dist, const out_of_window = @addWithOverflow(dist, l.chain[next_chain_index].value); + // if (out_of_window == 1) break; + dist +%= l.chain[next_chain_index].int(); // wrapping for potential null bit + comptime assert(flate.history_len == PackedOptionalU15.int(.null_bit)); + // Also, doing >= flate.history_len gives worse codegen with LLVM 21. + if ((dist | l.chain[next_chain_index].int()) & flate.history_len != 0) break; + } + } + + c.addHash(i, h); + return .{ .dist = best_dist, .len = best_len }; +} + +fn clenHlen(freqs: [19]u16) u4 { + // Note that the first four codes (16, 17, 18, and 0) are always present. + if (builtin.mode != .ReleaseSmall and (std.simd.suggestVectorLength(u16) orelse 1) >= 8) { + const V = @Vector(16, u16); + const hlen_mul: V = comptime m: { + var hlen_mul: [16]u16 = undefined; + for (token.codegen_order[3..], 0..) |i, hlen| { + hlen_mul[i] = hlen; + } + break :m hlen_mul; + }; + const encoded = freqs[0..16].* != @as(V, @splat(0)); + return @intCast(@reduce(.Max, @intFromBool(encoded) * hlen_mul)); + } else { + var max: u4 = 0; + for (token.codegen_order[4..], 1..) |i, len| { + max = if (freqs[i] == 0) max else @intCast(len); + } + return max; + } +} + +test clenHlen { + var freqs: [19]u16 = @splat(0); + try std.testing.expectEqual(0, clenHlen(freqs)); + for (token.codegen_order, 1..) |i, len| { + freqs[i] = 1; + try std.testing.expectEqual(len -| 4, clenHlen(freqs)); + freqs[i] = 0; + } +} + +/// Returns the number of values followed by the bitsize of the extra bits. +fn buildClen( + dyn_bits: []const u4, + out_values: []u8, + out_extra: []u8, + out_freqs: *[19]u16, +) struct { u16, u16 } { + assert(dyn_bits.len <= out_values.len); + assert(out_values.len == out_extra.len); + + var len: u16 = 0; + var extra_bitsize: u16 = 0; + + var remaining_bits = dyn_bits; + var prev: u4 = 0; + while (true) { + const b = remaining_bits[0]; + const n_max = @min(@as(u8, if (b != 0) + if (b != prev) 1 else 6 + else + 138), remaining_bits.len); + prev = b; + + var n: u8 = 0; + while (true) { + remaining_bits = remaining_bits[1..]; + n += 1; + if (n == n_max or remaining_bits[0] != b) break; + } + const code, const extra, const xsize = switch (n) { + 0 => unreachable, + 1...2 => .{ b, 0, 0 }, + 3...10 => .{ + @as(u8, 16) + @intFromBool(b == 0), + n - 3, + @as(u8, 2) + @intFromBool(b == 0), + }, + 11...138 => .{ 18, n - 11, 7 }, + else => unreachable, + }; + while (true) { + out_values[len] = code; + out_extra[len] = extra; + out_freqs[code] += 1; + extra_bitsize += xsize; + len += 1; + if (n != 2) { + @branchHint(.likely); + break; + } + // Code needs outputted once more + n = 1; + } + if (remaining_bits.len == 0) break; + } + + return .{ len, extra_bitsize }; +} + +test buildClen { + //dyn_bits: []u4, + //out_values: *[288 + 30]u8, + //out_extra: *[288 + 30]u8, + //out_freqs: *[19]u16, + //struct { u16, u16 } + var out_values: [288 + 30]u8 = undefined; + var out_extra: [288 + 30]u8 = undefined; + var out_freqs: [19]u16 = @splat(0); + const len, const extra_bitsize = buildClen(&([_]u4{ + 1, // A + 2, 2, // B + 3, 3, 3, // C + 4, 4, 4, 4, // D + 5, // E + 5, 5, 5, 5, 5, 5, // + 5, 5, 5, 5, 5, 5, + 5, 5, + 0, 1, // F + 0, 0, 1, // G + } ++ @as([138 + 10]u4, @splat(0)) // H + ), &out_values, &out_extra, &out_freqs); + try std.testing.expectEqualSlices(u8, &.{ + 1, // A + 2, 2, // B + 3, 3, 3, // C + 4, 16, // D + 5, 16, 16, 5, 5, // E + 0, 1, // F + 0, 0, 1, // G + 18, 17, // H + }, out_values[0..len]); + try std.testing.expectEqualSlices(u8, &.{ + 0, // A + 0, 0, // B + 0, 0, 0, // C + 0, (0), // D + 0, (3), (3), 0, 0, // E + 0, 0, // F + 0, 0, 0, // G + (127), (7), // H + }, out_extra[0..len]); + try std.testing.expectEqual(2 + 2 + 2 + 7 + 3, extra_bitsize); + try std.testing.expectEqualSlices(u16, &.{ + 3, 3, 2, 3, 1, 3, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 3, 1, 1, + }, &out_freqs); +} + +fn writeBlock(c: *Compress, eos: bool) Writer.Error!void { + const toks = &c.buffered_tokens; + if (!eos) assert(toks.n == block_tokens); + assert(toks.lit_freqs[256] == 0); + toks.lit_freqs[256] = 1; + + var dyn_codes_buf: [286 + 30]u16 = undefined; + var dyn_bits_buf: [286 + 30]u4 = @splat(0); + + const dyn_lit_codes_bitsize, const dyn_last_lit = huffman.build( + &toks.lit_freqs, + dyn_codes_buf[0..286], + dyn_bits_buf[0..286], + 15, + true, + ); + const dyn_lit_len = @max(257, dyn_last_lit + 1); + + const dyn_dist_codes_bitsize, const dyn_last_dist = huffman.build( + &toks.dist_freqs, + dyn_codes_buf[dyn_lit_len..][0..30], + dyn_bits_buf[dyn_lit_len..][0..30], + 15, + true, + ); + const dyn_dist_len = @max(1, dyn_last_dist + 1); + + var clen_values: [288 + 30]u8 = undefined; + var clen_extra: [288 + 30]u8 = undefined; + var clen_freqs: [19]u16 = @splat(0); + const clen_len, const clen_extra_bitsize = buildClen( + dyn_bits_buf[0 .. dyn_lit_len + dyn_dist_len], + &clen_values, + &clen_extra, + &clen_freqs, + ); + + var clen_codes: [19]u16 = undefined; + var clen_bits: [19]u4 = @splat(0); + const clen_codes_bitsize, _ = huffman.build( + &clen_freqs, + &clen_codes, + &clen_bits, + 7, + false, + ); + const hclen = clenHlen(clen_freqs); + + const dynamic_bitsize = @as(u32, 14) + + (4 + @as(u6, hclen)) * 3 + clen_codes_bitsize + clen_extra_bitsize + + dyn_lit_codes_bitsize + dyn_dist_codes_bitsize; + const fixed_bitsize = n: { + const freq7 = 1; // eos + var freq8: u16 = 0; + var freq9: u16 = 0; + var freq12: u16 = 0; // 7 + 5 - match freqs always have corresponding 5-bit dist freq + var freq13: u16 = 0; // 8 + 5 + for (toks.lit_freqs[0..144]) |f| freq8 += f; + for (toks.lit_freqs[144..256]) |f| freq9 += f; + assert(toks.lit_freqs[256] == 1); + for (toks.lit_freqs[257..280]) |f| freq12 += f; + for (toks.lit_freqs[280..286]) |f| freq13 += f; + break :n @as(u32, freq7) * 7 + + @as(u32, freq8) * 8 + @as(u32, freq9) * 9 + + @as(u32, freq12) * 12 + @as(u32, freq13) * 13; + }; + + stored: { + for (toks.dist_freqs) |n| if (n != 0) break :stored; + // No need to check len frequencies since they each have a corresponding dist frequency + assert(for (toks.lit_freqs[257..]) |f| (if (f != 0) break false) else true); + + // No matches. If the stored size is smaller than the huffman-encoded version, it will be + // outputed in a store block. This is not done with matches since the original input would + // need to be stored since the window may slid, and it may also exceed 65535 bytes. This + // should be OK since most inputs with matches should be more compressable anyways. + const stored_align_bits = -%(c.bit_writer.buffered_n +% 3); + const stored_bitsize = stored_align_bits + @as(u32, 32) + @as(u32, toks.n) * 8; + if (@min(dynamic_bitsize, fixed_bitsize) < stored_bitsize) break :stored; + + try c.bit_writer.write(BlockHeader.int(.{ .kind = .stored, .final = eos }), 3); + try c.bit_writer.output.rebase(0, 5); + c.bit_writer.byteAlign(); + c.bit_writer.output.writeInt(u16, c.buffered_tokens.n, .little) catch unreachable; + c.bit_writer.output.writeInt(u16, ~c.buffered_tokens.n, .little) catch unreachable; + + // Relatively small buffer since regular draining will + // always consume slightly less than 2 << 15 bytes. + var vec_buf: [4][]const u8 = undefined; + var vec_n: usize = 0; + var i: usize = 0; + + assert(c.buffered_tokens.pos != 0); + while (i != c.buffered_tokens.pos) { + const h: TokenBufferEntryHeader = @bitCast(toks.list[i..][0..2].*); + assert(h.kind == .bytes); + + i += 2; + vec_buf[vec_n] = toks.list[i..][0..h.data]; + i += h.data; + + vec_n += 1; + if (i == c.buffered_tokens.pos or vec_n == vec_buf.len) { + try c.bit_writer.output.writeVecAll(vec_buf[0..vec_n]); + vec_n = 0; + } + } + + toks.* = .empty; + return; + } + + const lit_codes, const lit_bits, const dist_codes, const dist_bits = + if (dynamic_bitsize < fixed_bitsize) codes: { + try c.bit_writer.write(BlockHeader.Dynamic.int(.{ + .regular = .{ .final = eos, .kind = .dynamic }, + .hlit = @intCast(dyn_lit_len - 257), + .hdist = @intCast(dyn_dist_len - 1), + .hclen = hclen, + }), 17); + try c.bit_writer.writeClen( + hclen, + clen_values[0..clen_len], + clen_extra[0..clen_len], + clen_codes, + clen_bits, + ); + break :codes .{ + dyn_codes_buf[0..dyn_lit_len], + dyn_bits_buf[0..dyn_lit_len], + dyn_codes_buf[dyn_lit_len..][0..dyn_dist_len], + dyn_bits_buf[dyn_lit_len..][0..dyn_dist_len], + }; + } else codes: { + try c.bit_writer.write(BlockHeader.int(.{ .final = eos, .kind = .fixed }), 3); + break :codes .{ + &token.fixed_lit_codes, + &token.fixed_lit_bits, + &token.fixed_dist_codes, + &token.fixed_dist_bits, + }; + }; + + var i: usize = 0; + while (i != toks.pos) { + const h: TokenBufferEntryHeader = @bitCast(toks.list[i..][0..2].*); + i += 2; + if (h.kind == .bytes) { + for (toks.list[i..][0..h.data]) |b| { + try c.bit_writer.write(lit_codes[b], lit_bits[b]); + } + i += h.data; + } else { + const dist = h.data; + const len = toks.list[i]; + i += 1; + const dist_code = token.DistCode.fromVal(dist); + const len_code = token.LenCode.fromVal(len); + const dist_val = dist_code.toInt(); + const lit_val = @as(u16, 257) + len_code.toInt(); + + var out: u48 = lit_codes[lit_val]; + var out_bits: u6 = lit_bits[lit_val]; + out |= @shlExact(@as(u20, len - len_code.base()), @intCast(out_bits)); + out_bits += len_code.extraBits(); + + out |= @shlExact(@as(u35, dist_codes[dist_val]), out_bits); + out_bits += dist_bits[dist_val]; + out |= @shlExact(@as(u48, dist - dist_code.base()), out_bits); + out_bits += dist_code.extraBits(); + + try c.bit_writer.write(out, out_bits); + } + } + try c.bit_writer.write(lit_codes[256], lit_bits[256]); + + toks.* = .empty; +} + +/// Huffman tree construction. +/// +/// The approach for building the huffman tree is [taken from zlib] +/// (https://github.com/madler/zlib/blob/v1.3.1/trees.c#L625) with some modifications. +const huffman = struct { + const max_leafs = 286; + const max_nodes = max_leafs * 2; + + const Node = struct { + freq: u16, + depth: u16, + + pub const Index = u16; + + pub fn smaller(a: Node, b: Node) bool { + return if (a.freq != b.freq) a.freq < b.freq else a.depth < b.depth; + } + }; + + fn heapSiftDown(nodes: []Node, heap: []Node.Index, start: usize) void { + var i = start; + while (true) { + var min = i; + const l = i * 2 + 1; + const r = l + 1; + min = if (l < heap.len and nodes[heap[l]].smaller(nodes[heap[min]])) l else min; + min = if (r < heap.len and nodes[heap[r]].smaller(nodes[heap[min]])) r else min; + if (i == min) break; + mem.swap(Node.Index, &heap[i], &heap[min]); + i = min; + } + } + + fn heapRemoveRoot(nodes: []Node, heap: []Node.Index) void { + heap[0] = heap[heap.len - 1]; + heapSiftDown(nodes, heap[0 .. heap.len - 1], 0); + } + + /// Returns the total bits to encode `freqs` followed by the index of the last non-zero bits. + /// For `freqs[i]` == 0, `out_codes[i]` will be undefined. + /// It is asserted `out_bits` is zero-filled. + /// It is asserted `out_bits.len` is at least a length of + /// one if ncomplete trees are allowed and two otherwise. + pub fn build( + freqs: []const u16, + out_codes: []u16, + out_bits: []u4, + max_bits: u4, + incomplete_allowed: bool, + ) struct { u32, u16 } { + assert(out_codes.len - 1 >= @intFromBool(incomplete_allowed)); + // freqs and out_codes are in the loop to assert they are all the same length + for (freqs, out_codes, out_bits) |_, _, n| assert(n == 0); + assert(out_codes.len <= @as(u16, 1) << max_bits); + + // Indexes 0..freqs are leafs, indexes max_leafs.. are internal nodes. + var tree_nodes: [max_nodes]Node = undefined; + var tree_parent_nodes: [max_nodes]Node.Index = undefined; + var nodes_end: u16 = max_leafs; + // Dual-purpose buffer. Nodes are ordered by least frequency or when equal, least depth. + // The start is a min heap of level-zero nodes. + // The end is a sorted buffer of nodes with the greatest first. + var node_buf: [max_nodes]Node.Index = undefined; + var heap_end: u16 = 0; + var sorted_start: u16 = node_buf.len; + + for (0.., freqs) |n, freq| { + tree_nodes[n] = .{ .freq = freq, .depth = 0 }; + node_buf[heap_end] = @intCast(n); + heap_end += @intFromBool(freq != 0); + } + + // There must be at least one code at minimum, + node_buf[heap_end] = 0; + heap_end += @intFromBool(heap_end == 0); + // and at least two if incomplete must be avoided. + if (heap_end == 1 and incomplete_allowed) { + @branchHint(.unlikely); // LLVM 21 optimizes this branch as the more likely without + + // Codes must have at least one-bit, so this is a special case. + out_bits[node_buf[0]] = 1; + out_codes[node_buf[0]] = 0; + return .{ freqs[node_buf[0]], node_buf[0] }; + } + const last_nonzero = @max(node_buf[heap_end - 1], 1); // For heap_end > 1, last is not be 0 + node_buf[heap_end] = @intFromBool(node_buf[0] == 0); + heap_end += @intFromBool(heap_end == 1); + + // Heapify the array of frequencies + const heapify_final = heap_end - 1; + const heapify_start = (heapify_final - 1) / 2; // Parent of final node + var heapify_i = heapify_start; + while (true) { + heapSiftDown(&tree_nodes, node_buf[0..heap_end], heapify_i); + if (heapify_i == 0) break; + heapify_i -= 1; + } + + // Build optimal tree. `max_bits` is not enforced yet. + while (heap_end > 1) { + const a = node_buf[0]; + heapRemoveRoot(&tree_nodes, node_buf[0..heap_end]); + heap_end -= 1; + const b = node_buf[0]; + + sorted_start -= 2; + node_buf[sorted_start..][0..2].* = .{ b, a }; + + tree_nodes[nodes_end] = .{ + .freq = tree_nodes[a].freq + tree_nodes[b].freq, + .depth = @max(tree_nodes[a].depth, tree_nodes[b].depth) + 1, + }; + defer nodes_end += 1; + tree_parent_nodes[a] = nodes_end; + tree_parent_nodes[b] = nodes_end; + + node_buf[0] = nodes_end; + heapSiftDown(&tree_nodes, node_buf[0..heap_end], 0); + } + sorted_start -= 1; + node_buf[sorted_start] = node_buf[0]; + + var bit_counts: [16]u16 = @splat(0); + buildBits(out_bits, &bit_counts, &tree_parent_nodes, node_buf[sorted_start..], max_bits); + return .{ buildValues(freqs, out_codes, out_bits, bit_counts), last_nonzero }; + } + + fn buildBits( + out_bits: []u4, + bit_counts: *[16]u16, + parent_nodes: *[max_nodes]Node.Index, + sorted: []Node.Index, + max_bits: u4, + ) void { + var internal_node_bits: [max_nodes - max_leafs]u4 = undefined; + var overflowed: u16 = 0; + + internal_node_bits[sorted[0] - max_leafs] = 0; // root + for (sorted[1..]) |i| { + const parent_bits = internal_node_bits[parent_nodes[i] - max_leafs]; + overflowed += @intFromBool(parent_bits == max_bits); + const bits = parent_bits + @intFromBool(parent_bits != max_bits); + bit_counts[bits] += @intFromBool(i < max_leafs); + (if (i >= max_leafs) &internal_node_bits[i - max_leafs] else &out_bits[i]).* = bits; + } + + if (overflowed == 0) { + @branchHint(.likely); + return; + } + + outer: while (true) { + var deepest: u4 = max_bits - 1; + while (bit_counts[deepest] == 0) deepest -= 1; + while (overflowed != 0) { + // Insert an internal node under the leaf and move an overflow as its sibling + bit_counts[deepest] -= 1; + bit_counts[deepest + 1] += 2; + // Only overflow moved. Its sibling's depth is one less, however is still >= depth. + bit_counts[max_bits] -= 1; + overflowed -= 2; + + if (overflowed == 0) break :outer; + deepest += 1; + if (deepest == max_bits) continue :outer; + } + } + + // Reassign bit lengths + assert(bit_counts[0] == 0); + var i: usize = 0; + for (1.., bit_counts[1..]) |bits, all| { + var remaining = all; + while (remaining != 0) { + defer i += 1; + if (sorted[i] >= max_leafs) continue; + out_bits[sorted[i]] = @intCast(bits); + remaining -= 1; + } + } + assert(for (sorted[i..]) |n| { // all leafs consumed + if (n < max_leafs) break false; + } else true); + } + + fn buildValues(freqs: []const u16, out_codes: []u16, bits: []u4, bit_counts: [16]u16) u32 { + var code: u16 = 0; + var base: [16]u16 = undefined; + assert(bit_counts[0] == 0); + for (bit_counts[1..], base[1..]) |c, *b| { + b.* = code; + code +%= c; + code <<= 1; + } + var freq_sums: [16]u16 = @splat(0); + for (out_codes, bits, freqs) |*c, b, f| { + c.* = @bitReverse(base[b]) >> -%b; + base[b] += 1; // For `b == 0` this is fine since v is specified to be undefined. + freq_sums[b] += f; + } + return @reduce(.Add, @as(@Vector(16, u32), freq_sums) * std.simd.iota(u32, 16)); + } + + test build { + var codes: [8]u16 = undefined; + var bits: [8]u4 = undefined; + + const regular_freqs: [8]u16 = .{ 1, 1, 0, 8, 8, 0, 2, 4 }; + // The optimal tree for the above frequencies is + // 4 1 1 + // \ / + // 3 2 # + // \ / + // 2 8 8 4 # + // \ / \ / + // 1 # # + // \ / + // 0 # + bits = @splat(0); + var n, var lnz = build(®ular_freqs, &codes, &bits, 15, true); + codes[2] = 0; + codes[5] = 0; + try std.testing.expectEqualSlices(u4, &.{ 4, 4, 0, 2, 2, 0, 3, 2 }, &bits); + try std.testing.expectEqualSlices(u16, &.{ + 0b0111, 0b1111, 0, 0b00, 0b10, 0, 0b011, 0b01, + }, &codes); + try std.testing.expectEqual(54, n); + try std.testing.expectEqual(7, lnz); + // When constrained to 3 bits, it becomes + // 3 1 1 2 4 + // \ / \ / + // 2 8 8 # # + // \ / \ / + // 1 # # + // \ / + // 0 # + bits = @splat(0); + n, lnz = build(®ular_freqs, &codes, &bits, 3, true); + codes[2] = 0; + codes[5] = 0; + try std.testing.expectEqualSlices(u4, &.{ 3, 3, 0, 2, 2, 0, 3, 3 }, &bits); + try std.testing.expectEqualSlices(u16, &.{ + 0b001, 0b101, 0, 0b00, 0b10, 0, 0b011, 0b111, + }, &codes); + try std.testing.expectEqual(56, n); + try std.testing.expectEqual(7, lnz); + + // Empty tree. At least one code should be present + bits = @splat(0); + n, lnz = build(&.{ 0, 0 }, codes[0..2], bits[0..2], 15, true); + try std.testing.expectEqualSlices(u4, &.{ 1, 0 }, bits[0..2]); + try std.testing.expectEqual(0b0, codes[0]); + try std.testing.expectEqual(0, n); + try std.testing.expectEqual(0, lnz); + + // Check all incompletable frequencies are completed + for ([_][2]u16{ .{ 0, 0 }, .{ 0, 1 }, .{ 1, 0 } }) |incomplete| { + // Empty tree. Both codes should be present to prevent incomplete trees + bits = @splat(0); + n, lnz = build(&incomplete, codes[0..2], bits[0..2], 15, false); + try std.testing.expectEqualSlices(u4, &.{ 1, 1 }, bits[0..2]); + try std.testing.expectEqualSlices(u16, &.{ 0b0, 0b1 }, codes[0..2]); + try std.testing.expectEqual(incomplete[0] + incomplete[1], n); + try std.testing.expectEqual(1, lnz); + } + + try std.testing.fuzz({}, checkFuzzedBuildFreqs, .{}); } - const buffered = me.buffered(); - const min_lookahead = Token.min_length + Token.max_length; - const history_plus_lookahead_len = flate.history_len + min_lookahead; - if (buffered.len < history_plus_lookahead_len) return 0; - const lookahead = buffered[flate.history_len..]; + fn checkFuzzedBuildFreqs(_: void, freqs: []const u8) !void { + @disableInstrumentation(); + var r: Io.Reader = .fixed(freqs); + var freqs_limit: u16 = 65535; + var freqs_buf: [max_leafs]u16 = undefined; + var nfreqs: u15 = 0; + + const params: packed struct(u8) { + max_bits: u4, + _: u3, + incomplete_allowed: bool, + } = @bitCast(r.takeByte() catch 255); + while (nfreqs != freqs_buf.len) { + const leb = r.takeLeb128(u16); + const f = if (leb) |f| @min(f, freqs_limit) else |e| switch (e) { + error.ReadFailed => unreachable, + error.EndOfStream => 0, + error.Overflow => freqs_limit, + }; + freqs_buf[nfreqs] = f; + nfreqs += 1; + freqs_limit -= f; + if (leb == error.EndOfStream and nfreqs - 1 > @intFromBool(params.incomplete_allowed)) + break; + } + + var codes_buf: [max_leafs]u16 = undefined; + var bits_buf: [max_leafs]u4 = @splat(0); + const total_bits, const last_nonzero = build( + freqs_buf[0..nfreqs], + codes_buf[0..nfreqs], + bits_buf[0..nfreqs], + @max(math.log2_int_ceil(u15, nfreqs), params.max_bits), + params.incomplete_allowed, + ); + + var has_bitlen_one: bool = false; + var expected_total_bits: u32 = 0; + var expected_last_nonzero: ?u16 = null; + var weighted_sum: u32 = 0; + for (freqs_buf[0..nfreqs], bits_buf[0..nfreqs], 0..) |f, nb, i| { + has_bitlen_one = has_bitlen_one or nb == 1; + weighted_sum += @shlExact(@as(u16, 1), 15 - nb) & ((1 << 15) - 1); + expected_total_bits += @as(u32, f) * nb; + if (nb != 0) expected_last_nonzero = @intCast(i); + } + + errdefer std.log.err( + \\ params: {} + \\ freqs: {any} + \\ bits: {any} + \\ # freqs: {} + \\ max bits: {} + \\ weighted sum: {} + \\ has_bitlen_one: {} + \\ expected/actual total bits: {}/{} + \\ expected/actual last nonzero: {?}/{} + ++ "\n", .{ + params, + freqs_buf[0..nfreqs], + bits_buf[0..nfreqs], + nfreqs, + @max(math.log2_int_ceil(u15, nfreqs), params.max_bits), + weighted_sum, + has_bitlen_one, + expected_total_bits, + total_bits, + expected_last_nonzero, + last_nonzero, + }); + + try std.testing.expectEqual(expected_total_bits, total_bits); + try std.testing.expectEqual(expected_last_nonzero, last_nonzero); + if (weighted_sum > 1 << 15) + return error.OversubscribedHuffmanTree; + if (weighted_sum < 1 << 15 and + !(params.incomplete_allowed and has_bitlen_one and weighted_sum == 1 << 14)) + return error.IncompleteHuffmanTree; + } +}; - // TODO tokenize - _ = lookahead; - //c.hasher.update(lookahead[0..n]); - @panic("TODO"); +test { + _ = huffman; } -pub fn end(c: *Compress) !void { - try endUnflushed(c); - const out = c.block_writer.output; - try out.flush(); +/// [0] is a gradient where the probability of lower values decreases across it +/// [1] is completely random and hence uncompressable +fn testingFreqBufs() !*[2][65536]u8 { + const fbufs = try std.testing.allocator.create([2][65536]u8); + var prng: std.Random.DefaultPrng = .init(std.testing.random_seed); + prng.random().bytes(&fbufs[0]); + prng.random().bytes(&fbufs[1]); + for (0.., &fbufs[0], fbufs[1]) |i, *grad, rand| { + const prob = @as(u8, @intCast(255 - i / (fbufs[0].len * 256))); + grad.* /= @max(1, rand / @max(1, prob)); + } + return fbufs; } -pub fn endUnflushed(c: *Compress) !void { - while (c.writer.end != 0) _ = try drain(&c.writer, &.{""}, 1); - c.state = .ended; +fn testingCheckDecompressedMatches( + flate_bytes: []const u8, + expected_size: u32, + expected_hash: flate.Container.Hasher, +) !void { + const container: flate.Container = expected_hash; + var data_hash: flate.Container.Hasher = .init(container); + var data_size: u32 = 0; + var flate_r: Io.Reader = .fixed(flate_bytes); + var deflate_buf: [flate.max_window_len]u8 = undefined; + var deflate: flate.Decompress = .init(&flate_r, container, &deflate_buf); - const out = c.block_writer.output; + while (deflate.reader.peekGreedy(1)) |bytes| { + data_size += @intCast(bytes.len); + data_hash.update(bytes); + deflate.reader.toss(bytes.len); + } else |e| switch (e) { + error.ReadFailed => return deflate.err.?, + error.EndOfStream => {}, + } - // TODO flush tokens + try testingCheckContainerHash( + expected_size, + expected_hash, + data_hash, + data_size, + deflate.container_metadata, + ); +} - switch (c.hasher) { - .gzip => |*gzip| { - // GZIP 8 bytes footer - // - 4 bytes, CRC32 (CRC-32) - // - 4 bytes, ISIZE (Input SIZE) - size of the original (uncompressed) input data modulo 2^32 - const footer = try out.writableArray(8); - std.mem.writeInt(u32, footer[0..4], gzip.crc.final(), .little); - std.mem.writeInt(u32, footer[4..8], @truncate(gzip.count), .little); +fn testingCheckContainerHash( + expected_size: u32, + expected_hash: flate.Container.Hasher, + actual_hash: flate.Container.Hasher, + actual_size: u32, + actual_meta: flate.Container.Metadata, +) !void { + try std.testing.expectEqual(expected_size, actual_size); + switch (actual_hash) { + .raw => {}, + .gzip => |gz| { + const expected_crc = expected_hash.gzip.crc.final(); + try std.testing.expectEqual(expected_size, actual_meta.gzip.count); + try std.testing.expectEqual(expected_crc, gz.crc.final()); + try std.testing.expectEqual(expected_crc, actual_meta.gzip.crc); }, - .zlib => |*zlib| { - // ZLIB (RFC 1950) is big-endian, unlike GZIP (RFC 1952). - // 4 bytes of ADLER32 (Adler-32 checksum) - // Checksum value of the uncompressed data (excluding any - // dictionary data) computed according to Adler-32 - // algorithm. - std.mem.writeInt(u32, try out.writableArray(4), zlib.adler, .big); + .zlib => |zl| { + const expected_adler = expected_hash.zlib.adler; + try std.testing.expectEqual(expected_adler, zl.adler); + try std.testing.expectEqual(expected_adler, actual_meta.zlib.adler); }, - .raw => {}, } } -pub const Simple = struct { - /// Note that store blocks are limited to 65535 bytes. - buffer: []u8, - wp: usize, - block_writer: BlockWriter, - hasher: Container.Hasher, - strategy: Strategy, +const PackedContainer = packed struct(u2) { + raw: bool, + other: enum(u1) { gzip, zlib }, + + pub fn val(c: @This()) flate.Container { + return if (c.raw) .raw else switch (c.other) { + .gzip => .gzip, + .zlib => .zlib, + }; + } +}; + +test Compress { + const fbufs = try testingFreqBufs(); + defer if (!builtin.fuzz) std.testing.allocator.destroy(fbufs); + try std.testing.fuzz(fbufs, testFuzzedCompressInput, .{}); +} + +fn testFuzzedCompressInput(fbufs: *const [2][65536]u8, input: []const u8) !void { + var in: Io.Reader = .fixed(input); + var opts: packed struct(u51) { + container: PackedContainer, + buf_size: u16, + good: u8, + nice: u8, + lazy: u8, + /// Not a `u16` to limit it for performance + chain: u9, + } = @bitCast(in.takeLeb128(u51) catch 0); + var expected_hash: flate.Container.Hasher = .init(opts.container.val()); + var expected_size: u32 = 0; + + var flate_buf: [128 * 1024]u8 = undefined; + var flate_w: Writer = .fixed(&flate_buf); + var deflate_buf: [flate.max_window_len * 2]u8 = undefined; + var deflate_w = try Compress.init( + &flate_w, + deflate_buf[0 .. flate.max_window_len + @as(usize, opts.buf_size)], + opts.container.val(), + .{ + .good = @as(u16, opts.good) + 3, + .nice = @as(u16, opts.nice) + 3, + .lazy = @as(u16, @min(opts.lazy, opts.nice)) + 3, + .chain = @max(1, opts.chain, @as(u8, 4) * @intFromBool(opts.good <= opts.lazy)), + }, + ); + + // It is ensured that more bytes are not written then this to ensure this run + // does not take too long and that `flate_buf` does not run out of space. + const flate_buf_blocks = flate_buf.len / block_tokens; + // Allow a max overhead of 64 bytes per block since the implementation does not gaurauntee it + // writes store blocks when optimal. This comes from taking less than 32 bytes to write an + // optimal dynamic block header of mostly bitlen 8 codes and the end of block literal plus + // `(65536 / 256) / 8`, which is is the maximum number of extra bytes from bitlen 9 codes. An + // extra 32 bytes is reserved on top of that for container headers and footers. + const max_size = flate_buf.len - (flate_buf_blocks * 64 + 32); + + while (true) { + const data: packed struct(u36) { + is_rebase: bool, + is_bytes: bool, + params: packed union { + copy: packed struct(u34) { + len_lo: u5, + dist: u15, + len_hi: u4, + _: u10, + }, + bytes: packed struct(u34) { + kind: enum(u1) { gradient, random }, + off_hi: u4, + len_lo: u10, + off_mi: u4, + len_hi: u5, + off_lo: u8, + _: u2, + }, + rebase: packed struct(u34) { + preserve: u17, + capacity: u17, + }, + }, + } = @bitCast(in.takeLeb128(u36) catch |e| switch (e) { + error.ReadFailed => unreachable, + error.Overflow => 0, + error.EndOfStream => break, + }); + + const buffered = deflate_w.writer.buffered(); + // Required for repeating patterns and since writing from `buffered` is illegal + var copy_buf: [512]u8 = undefined; + + if (data.is_rebase) { + const usable_capacity = deflate_w.writer.buffer.len - rebase_reserved_capacity; + const preserve = @min(data.params.rebase.preserve, usable_capacity); + const capacity = @min(data.params.rebase.capacity, usable_capacity - + @max(rebase_min_preserve, preserve)); + try deflate_w.writer.rebase(preserve, capacity); + continue; + } + + const max_bytes = max_size -| expected_size; + const bytes = if (!data.is_bytes and buffered.len != 0) bytes: { + const dist = @min(buffered.len, @as(u32, data.params.copy.dist) + 1); + const len = @min( + @max(@shlExact(@as(u9, data.params.copy.len_hi), 5) | data.params.copy.len_lo, 1), + max_bytes, + ); + // Reuse the implementation's history. Otherwise our own would need maintained. + const bytes_start = buffered[buffered.len - dist ..]; + const history_bytes = bytes_start[0..@min(bytes_start.len, len)]; + + @memcpy(copy_buf[0..history_bytes.len], history_bytes); + const new_history = len - history_bytes.len; + if (history_bytes.len != len) for ( // check needed for `- dist` + copy_buf[history_bytes.len..][0..new_history], + copy_buf[history_bytes.len - dist ..][0..new_history], + ) |*next, prev| { + next.* = prev; + }; + break :bytes copy_buf[0..len]; + } else bytes: { + const off = @shlExact(@as(u16, data.params.bytes.off_hi), 12) | + @shlExact(@as(u16, data.params.bytes.off_mi), 8) | + data.params.bytes.off_lo; + const len = @shlExact(@as(u16, data.params.bytes.len_hi), 10) | + data.params.bytes.len_lo; + const fbuf = &fbufs[@intFromEnum(data.params.bytes.kind)]; + break :bytes fbuf[off..][0..@min(len, fbuf.len - off, max_bytes)]; + }; + assert(bytes.len <= max_bytes); + try deflate_w.writer.writeAll(bytes); + expected_hash.update(bytes); + expected_size += @intCast(bytes.len); + } + + try deflate_w.writer.flush(); + try testingCheckDecompressedMatches(flate_w.buffered(), expected_size, expected_hash); +} + +/// Does not compress data +pub const Raw = struct { + /// After `flush` is called, all vtable calls with result in `error.WriteFailed.` + writer: Writer, + output: *Writer, + hasher: flate.Container.Hasher, - pub const Strategy = enum { huffman, store }; + const max_block_size: u16 = 65535; + const full_header: [5]u8 = .{ + BlockHeader.int(.{ .final = false, .kind = .stored }), + 255, + 255, + 0, + 0, + }; - pub fn init(output: *Writer, buffer: []u8, container: Container, strategy: Strategy) !Simple { - const header = container.header(); - try output.writeAll(header); + /// While there is no minimum buffer size, it is recommended + /// to be at least `flate.max_window_len` for optimal output. + pub fn init(output: *Writer, buffer: []u8, container: flate.Container) Writer.Error!Raw { + try output.writeAll(container.header()); return .{ - .buffer = buffer, - .wp = 0, - .block_writer = .init(output), + .writer = .{ + .buffer = buffer, + .vtable = &.{ + .drain = Raw.drain, + .flush = Raw.flush, + .rebase = Raw.rebase, + }, + }, + .output = output, .hasher = .init(container), - .strategy = strategy, }; } - pub fn flush(self: *Simple) !void { - try self.flushBuffer(false); - try self.block_writer.storedBlock("", false); - try self.block_writer.flush(); + fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize { + errdefer w.* = .failing; + const r: *Raw = @fieldParentPtr("writer", w); + const min_block = @min(w.buffer.len, max_block_size); + const pattern = data[data.len - 1]; + var partial_header: [5]u8 = undefined; + + var vecs: [16][]const u8 = undefined; + var vecs_n: usize = 0; + const data_bytes = Writer.countSplat(data, splat); + const total_bytes = w.end + data_bytes; + var rem_bytes = total_bytes; + var rem_splat = splat; + var rem_data = data; + var rem_data_elem: []const u8 = w.buffered(); + + assert(rem_bytes > min_block); + while (rem_bytes > min_block) { // not >= to allow `min_block` blocks to be marked as final + // also, it handles the case of `min_block` being zero (no buffer) + const block_size: u16 = @min(rem_bytes, max_block_size); + rem_bytes -= block_size; + + if (vecs_n == vecs.len) { + try r.output.writeVecAll(&vecs); + vecs_n = 0; + } + vecs[vecs_n] = if (block_size == 65535) + &full_header + else header: { + partial_header[0] = BlockHeader.int(.{ .final = false, .kind = .stored }); + mem.writeInt(u16, partial_header[1..3], block_size, .little); + mem.writeInt(u16, partial_header[3..5], ~block_size, .little); + break :header &partial_header; + }; + vecs_n += 1; + + var block_limit: Io.Limit = .limited(block_size); + while (true) { + if (vecs_n == vecs.len) { + try r.output.writeVecAll(&vecs); + vecs_n = 0; + } + + const vec = block_limit.sliceConst(rem_data_elem); + vecs[vecs_n] = vec; + vecs_n += 1; + r.hasher.update(vec); + + const is_pattern = rem_splat != splat and vec.len == pattern.len; + if (is_pattern) assert(pattern.len != 0); // exceeded countSplat + + if (!is_pattern or rem_splat == 0 or pattern.len > @intFromEnum(block_limit) / 2) { + rem_data_elem = rem_data_elem[vec.len..]; + block_limit = block_limit.subtract(vec.len).?; + + if (rem_data_elem.len == 0) { + rem_data_elem = rem_data[0]; + if (rem_data.len != 1) { + rem_data = rem_data[1..]; + } else if (rem_splat != 0) { + rem_splat -= 1; + } else { + // All of `data` has been consumed. + assert(block_limit == .nothing); + assert(rem_bytes == 0); + // Since `rem_bytes` and `block_limit` are zero, these won't be used. + rem_data = undefined; + rem_data_elem = undefined; + rem_splat = undefined; + } + } + if (block_limit == .nothing) break; + } else { + const out_splat = @intFromEnum(block_limit) / pattern.len; + assert(out_splat >= 2); + + try r.output.writeSplatAll(vecs[0..vecs_n], out_splat); + for (1..out_splat) |_| r.hasher.update(vec); + + vecs_n = 0; + block_limit = block_limit.subtract(pattern.len * out_splat).?; + if (rem_splat >= out_splat) { + // `out_splat` contains `rem_data`, however one more needs subtracted + // anyways since the next pattern is also being taken. + rem_splat -= out_splat; + } else { + // All of `data` has been consumed. + assert(block_limit == .nothing); + assert(rem_bytes == 0); + // Since `rem_bytes` and `block_limit` are zero, these won't be used. + rem_data = undefined; + rem_data_elem = undefined; + rem_splat = undefined; + } + if (block_limit == .nothing) break; + } + } + } + + if (vecs_n != 0) { // can be the case if a splat was sent + try r.output.writeVecAll(vecs[0..vecs_n]); + } + + if (rem_bytes > data_bytes) { + assert(rem_bytes - data_bytes == rem_data_elem.len); + assert(&rem_data_elem[0] == &w.buffer[total_bytes - rem_bytes]); + } + return w.consume(total_bytes - rem_bytes); + } + + fn flush(w: *Writer) Writer.Error!void { + defer w.* = .failing; + try Raw.rebaseInner(w, 0, w.buffer.len, true); } - pub fn finish(self: *Simple) !void { - try self.flushBuffer(true); - try self.block_writer.flush(); - try self.hasher.container().writeFooter(&self.hasher, self.block_writer.output); + fn rebase(w: *Writer, preserve: usize, capacity: usize) Writer.Error!void { + errdefer w.* = .failing; + try Raw.rebaseInner(w, preserve, capacity, false); } - fn flushBuffer(self: *Simple, final: bool) !void { - const buf = self.buffer[0..self.wp]; - switch (self.strategy) { - .huffman => try self.block_writer.huffmanBlock(buf, final), - .store => try self.block_writer.storedBlock(buf, final), + fn rebaseInner(w: *Writer, preserve: usize, capacity: usize, eos: bool) Writer.Error!void { + const r: *Raw = @fieldParentPtr("writer", w); + assert(preserve + capacity <= w.buffer.len); + if (eos) assert(capacity == w.buffer.len); + + var partial_header: [5]u8 = undefined; + var footer_buf: [8]u8 = undefined; + const preserved = @min(w.end, preserve); + var remaining = w.buffer[0 .. w.end - preserved]; + + var vecs: [16][]const u8 = undefined; + var vecs_n: usize = 0; + while (remaining.len > max_block_size) { // not >= so there is always a block down below + if (vecs_n == vecs.len) { + try r.output.writeVecAll(&vecs); + vecs_n = 0; + } + vecs[vecs_n + 0] = &full_header; + vecs[vecs_n + 1] = remaining[0..max_block_size]; + r.hasher.update(vecs[vecs_n + 1]); + vecs_n += 2; + remaining = remaining[max_block_size..]; + } + + // eos check required for empty block + if (w.buffer.len - (remaining.len + preserved) < capacity or eos) { + // A partial write is necessary to reclaim enough buffer space + const block_size: u16 = @intCast(remaining.len); + partial_header[0] = BlockHeader.int(.{ .final = eos, .kind = .stored }); + mem.writeInt(u16, partial_header[1..3], block_size, .little); + mem.writeInt(u16, partial_header[3..5], ~block_size, .little); + + if (vecs_n == vecs.len) { + try r.output.writeVecAll(&vecs); + vecs_n = 0; + } + vecs[vecs_n + 0] = &partial_header; + vecs[vecs_n + 1] = remaining[0..block_size]; + r.hasher.update(vecs[vecs_n + 1]); + vecs_n += 2; + remaining = remaining[block_size..]; + assert(remaining.len == 0); + + if (eos and r.hasher != .raw) { + // the footer is done here instead of `flush` so it can be included in the vector + var footer_w: Writer = .fixed(&footer_buf); + r.hasher.writeFooter(&footer_w) catch unreachable; + assert(footer_w.end != 0); + + if (vecs_n == vecs.len) { + try r.output.writeVecAll(&vecs); + return r.output.writeAll(footer_w.buffered()); + } else { + vecs[vecs_n] = footer_w.buffered(); + vecs_n += 1; + } + } } - self.wp = 0; + + try r.output.writeVecAll(vecs[0..vecs_n]); + _ = w.consume(w.end - preserved - remaining.len); } }; -test "generate a Huffman code from an array of frequencies" { - var freqs: [19]u16 = [_]u16{ - 8, // 0 - 1, // 1 - 1, // 2 - 2, // 3 - 5, // 4 - 10, // 5 - 9, // 6 - 1, // 7 - 0, // 8 - 0, // 9 - 0, // 10 - 0, // 11 - 0, // 12 - 0, // 13 - 0, // 14 - 0, // 15 - 1, // 16 - 3, // 17 - 5, // 18 +test Raw { + const data_buf = try std.testing.allocator.create([4 * 65536]u8); + defer if (!builtin.fuzz) std.testing.allocator.destroy(data_buf); + var prng: std.Random.DefaultPrng = .init(std.testing.random_seed); + prng.random().bytes(data_buf); + try std.testing.fuzz(data_buf, testFuzzedRawInput, .{}); +} + +fn countVec(data: []const []const u8) usize { + var bytes: usize = 0; + for (data) |d| bytes += d.len; + return bytes; +} + +fn testFuzzedRawInput(data_buf: *const [4 * 65536]u8, input: []const u8) !void { + const HashedStoreWriter = struct { + writer: Writer, + state: enum { + header, + block_header, + block_body, + final_block_body, + footer, + end, + }, + block_remaining: u16, + container: flate.Container, + data_hash: flate.Container.Hasher, + data_size: usize, + footer_hash: u32, + footer_size: u32, + + pub fn init(buf: []u8, container: flate.Container) @This() { + return .{ + .writer = .{ + .vtable = &.{ + .drain = @This().drain, + .flush = @This().flush, + }, + .buffer = buf, + }, + .state = .header, + .block_remaining = 0, + .container = container, + .data_hash = .init(container), + .data_size = 0, + .footer_hash = undefined, + .footer_size = undefined, + }; + } + + /// Note that this implementation is somewhat dependent on the implementation of + /// `Raw` by expecting headers / footers to be continous in data elements. It + /// also expects the header to be the same as `flate.Container.header` and not + /// for multiple streams to be concatenated. + fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize { + errdefer w.* = .failing; + var h: *@This() = @fieldParentPtr("writer", w); + + var rem_splat = splat; + var rem_data = data; + var rem_data_elem: []const u8 = w.buffered(); + + data_loop: while (true) { + const wanted = switch (h.state) { + .header => h.container.headerSize(), + .block_header => 5, + .block_body, .final_block_body => h.block_remaining, + .footer => h.container.footerSize(), + .end => 1, + }; + + if (wanted != 0) { + while (rem_data_elem.len == 0) { + rem_data_elem = rem_data[0]; + if (rem_data.len != 1) { + rem_data = rem_data[1..]; + } else { + if (rem_splat == 0) { + break :data_loop; + } else { + rem_splat -= 1; + } + } + } + } + + const bytes = Io.Limit.limited(wanted).sliceConst(rem_data_elem); + rem_data_elem = rem_data_elem[bytes.len..]; + + switch (h.state) { + .header => { + if (bytes.len < wanted) + return error.WriteFailed; // header eos + if (!mem.eql(u8, bytes, h.container.header())) + return error.WriteFailed; // wrong header + h.state = .block_header; + }, + .block_header => { + if (bytes.len < wanted) + return error.WriteFailed; // store block header eos + const header: BlockHeader = @bitCast(@as(u3, @truncate(bytes[0]))); + if (header.kind != .stored) + return error.WriteFailed; // non-store block + const len = mem.readInt(u16, bytes[1..3], .little); + const nlen = mem.readInt(u16, bytes[3..5], .little); + if (nlen != ~len) + return error.WriteFailed; // wrong nlen + h.block_remaining = len; + h.state = if (!header.final) .block_body else .final_block_body; + }, + .block_body, .final_block_body => { + h.data_hash.update(bytes); + h.data_size += bytes.len; + h.block_remaining -= @intCast(bytes.len); + if (h.block_remaining == 0) { + h.state = if (h.state != .final_block_body) .block_header else .footer; + } + }, + .footer => { + if (bytes.len < wanted) + return error.WriteFailed; // footer eos + switch (h.container) { + .raw => {}, + .gzip => { + h.footer_hash = mem.readInt(u32, bytes[0..4], .little); + h.footer_size = mem.readInt(u32, bytes[4..8], .little); + }, + .zlib => { + h.footer_hash = mem.readInt(u32, bytes[0..4], .big); + }, + } + h.state = .end; + }, + .end => return error.WriteFailed, // data past end + } + } + + w.end = 0; + return Writer.countSplat(data, splat); + } + + fn flush(w: *Writer) Writer.Error!void { + defer w.* = .failing; // Clears buffer even if state hasn't reached `end` + _ = try @This().drain(w, &.{""}, 0); + } }; - var codes: [19]HuffmanEncoder.Code = undefined; - var enc: HuffmanEncoder = .{ - .codes = &codes, - .freq_cache = undefined, - .bit_count = undefined, - .lns = undefined, - .lfs = undefined, + var in: Io.Reader = .fixed(input); + const opts: packed struct(u19) { + container: PackedContainer, + buf_len: u17, + } = @bitCast(in.takeLeb128(u19) catch 0); + var output: HashedStoreWriter = .init(&.{}, opts.container.val()); + var r_buf: [2 * 65536]u8 = undefined; + var r: Raw = try .init( + &output.writer, + r_buf[0 .. opts.buf_len +% flate.max_window_len], + opts.container.val(), + ); + + var data_base: u18 = 0; + var expected_hash: flate.Container.Hasher = .init(opts.container.val()); + var expected_size: u32 = 0; + var vecs: [32][]const u8 = undefined; + var vecs_n: usize = 0; + + while (in.seek != in.end) { + const VecInfo = packed struct(u58) { + output: bool, + /// If set, `data_len` and `splat` are reinterpreted as `capacity` + /// and `preserve_len` respectively and `output` is treated as set. + rebase: bool, + block_aligning_len: bool, + block_aligning_splat: bool, + data_len: u18, + splat: u18, + data_off: u18, + }; + var vec_info: VecInfo = @bitCast(in.takeLeb128(u58) catch |e| switch (e) { + error.ReadFailed => unreachable, + error.Overflow, error.EndOfStream => 0, + }); + + { + const buffered = r.writer.buffered().len + countVec(vecs[0..vecs_n]); + const to_align = mem.alignForwardAnyAlign(usize, buffered, Raw.max_block_size) - buffered; + assert((buffered + to_align) % Raw.max_block_size == 0); + + if (vec_info.block_aligning_len) { + vec_info.data_len = @intCast(to_align); + } else if (vec_info.block_aligning_splat and vec_info.data_len != 0 and + to_align % vec_info.data_len == 0) + { + vec_info.splat = @divExact(@as(u18, @intCast(to_align)), vec_info.data_len) -% 1; + } + } + + var splat = if (vec_info.output and !vec_info.rebase) vec_info.splat +% 1 else 1; + add_vec: { + if (vec_info.rebase) break :add_vec; + if (expected_size +| math.mulWide(u18, vec_info.data_len, splat) > + 10 * (1 << 16)) + { + // Skip this vector to avoid this test taking too long. + // 10 maximum sized blocks is choosen as the limit since it is two more + // than the maximum the implementation can output in one drain. + splat = 1; + break :add_vec; + } + + vecs[vecs_n] = data_buf[@min( + data_base +% vec_info.data_off, + data_buf.len - vec_info.data_len, + )..][0..vec_info.data_len]; + + data_base +%= vec_info.data_len +% 3; // extra 3 to help catch aliasing bugs + + for (0..splat) |_| expected_hash.update(vecs[vecs_n]); + expected_size += @as(u32, @intCast(vecs[vecs_n].len)) * splat; + vecs_n += 1; + } + + const want_drain = vecs_n == vecs.len or vec_info.output or vec_info.rebase or + in.seek == in.end; + if (want_drain and vecs_n != 0) { + try r.writer.writeSplatAll(vecs[0..vecs_n], splat); + vecs_n = 0; + } else assert(splat == 1); + + if (vec_info.rebase) { + try r.writer.rebase(vec_info.data_len, @min( + r.writer.buffer.len -| vec_info.data_len, + vec_info.splat, + )); + } + } + + try r.writer.flush(); + try output.writer.flush(); + + try std.testing.expectEqual(.end, output.state); + try std.testing.expectEqual(expected_size, output.data_size); + switch (output.data_hash) { + .raw => {}, + .gzip => |gz| { + const expected_crc = expected_hash.gzip.crc.final(); + try std.testing.expectEqual(expected_crc, gz.crc.final()); + try std.testing.expectEqual(expected_crc, output.footer_hash); + try std.testing.expectEqual(expected_size, output.footer_size); + }, + .zlib => |zl| { + const expected_adler = expected_hash.zlib.adler; + try std.testing.expectEqual(expected_adler, zl.adler); + try std.testing.expectEqual(expected_adler, output.footer_hash); + }, + } +} + +/// Only performs huffman compression on data, does no matching. +pub const Huffman = struct { + writer: Writer, + bit_writer: BitWriter, + hasher: flate.Container.Hasher, + + const max_tokens: u16 = 65535 - 1; // one is reserved for EOF + + /// While there is no minimum buffer size, it is recommended + /// to be at least `flate.max_window_len` to improve compression. + /// + /// It is asserted `output` has a capacity of at least 8 bytes. + pub fn init(output: *Writer, buffer: []u8, container: flate.Container) Writer.Error!Huffman { + assert(output.buffer.len > 8); + + try output.writeAll(container.header()); + return .{ + .writer = .{ + .buffer = buffer, + .vtable = &.{ + .drain = Huffman.drain, + .flush = Huffman.flush, + .rebase = Huffman.rebase, + }, + }, + .bit_writer = .init(output), + .hasher = .init(container), + }; + } + + fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize { + { + //std.debug.print("drain {} (buffered)", .{w.buffered().len}); + //for (data) |d| std.debug.print("\n\t+ {}", .{d.len}); + //std.debug.print(" x {}\n\n", .{splat}); + } + + const h: *Huffman = @fieldParentPtr("writer", w); + const min_block = @min(w.buffer.len, max_tokens); + const pattern = data[data.len - 1]; + + const data_bytes = Writer.countSplat(data, splat); + const total_bytes = w.end + data_bytes; + var rem_bytes = total_bytes; + var rem_splat = splat; + var rem_data = data; + var rem_data_elem: []const u8 = w.buffered(); + + assert(rem_bytes > min_block); + while (rem_bytes > min_block) { // not >= to allow `min_block` blocks to be marked as final + // also, it handles the case of `min_block` being zero (no buffer) + const block_size: u16 = @min(rem_bytes, max_tokens); + rem_bytes -= block_size; + + // Count frequencies + comptime assert(max_tokens != 65535); + var freqs: [257]u16 = @splat(0); + freqs[256] = 1; + + const start_splat = rem_splat; + const start_data = rem_data; + const start_data_elem = rem_data_elem; + + var block_limit: Io.Limit = .limited(block_size); + while (true) { + const bytes = block_limit.sliceConst(rem_data_elem); + const is_pattern = rem_splat != splat and bytes.len == pattern.len; + + const mul = if (!is_pattern) 1 else @intFromEnum(block_limit) / pattern.len; + assert(mul != 0); + if (is_pattern) assert(mul <= rem_splat + 1); // one more for `rem_data` + + for (bytes) |b| freqs[b] += @intCast(mul); + rem_data_elem = rem_data_elem[bytes.len..]; + block_limit = block_limit.subtract(bytes.len * mul).?; + + if (rem_data_elem.len == 0) { + rem_data_elem = rem_data[0]; + if (rem_data.len != 1) { + rem_data = rem_data[1..]; + } else if (rem_splat >= mul) { + // if the counter was not the pattern, `mul` is always one, otherwise, + // `mul` contains `rem_data`, however one more needs subtracted anyways + // since the next pattern is also being taken. + rem_splat -= mul; + } else { + // All of `data` has been consumed. + assert(block_limit == .nothing); + assert(rem_bytes == 0); + // Since `rem_bytes` and `block_limit` are zero, these won't be used. + rem_data = undefined; + rem_data_elem = undefined; + rem_splat = undefined; + } + } + if (block_limit == .nothing) break; + } + + // Output block + rem_splat = start_splat; + rem_data = start_data; + rem_data_elem = start_data_elem; + block_limit = .limited(block_size); + + var codes_buf: CodesBuf = .init; + if (try h.outputHeader(&freqs, &codes_buf, block_size, false)) |table| { + while (true) { + const bytes = block_limit.sliceConst(rem_data_elem); + rem_data_elem = rem_data_elem[bytes.len..]; + block_limit = block_limit.subtract(bytes.len).?; + + h.hasher.update(bytes); + for (bytes) |b| { + try h.bit_writer.write(table.codes[b], table.bits[b]); + } + + if (rem_data_elem.len == 0) { + rem_data_elem = rem_data[0]; + if (rem_data.len != 1) { + rem_data = rem_data[1..]; + } else if (rem_splat != 0) { + rem_splat -= 1; + } else { + // All of `data` has been consumed. + assert(block_limit == .nothing); + assert(rem_bytes == 0); + // Since `rem_bytes` and `block_limit` are zero, these won't be used. + rem_data = undefined; + rem_data_elem = undefined; + rem_splat = undefined; + } + } + if (block_limit == .nothing) break; + } + try h.bit_writer.write(table.codes[256], table.bits[256]); + } else while (true) { + // Store block + + // Write data that is not a full vector element + const in_pattern = rem_splat != splat; + const vec_elem_i, const in_data = + @subWithOverflow(data.len - (rem_data.len - @intFromBool(in_pattern)), 1); + const is_elem = in_data == 0 and data[vec_elem_i].len == rem_data_elem.len; + + if (!is_elem or rem_data_elem.len > @intFromEnum(block_limit)) { + block_limit = block_limit.subtract(rem_data_elem.len) orelse { + try h.bit_writer.output.writeAll(rem_data_elem[0..@intFromEnum(block_limit)]); + h.hasher.update(rem_data_elem[0..@intFromEnum(block_limit)]); + rem_data_elem = rem_data_elem[@intFromEnum(block_limit)..]; + assert(rem_data_elem.len != 0); + break; + }; + try h.bit_writer.output.writeAll(rem_data_elem); + h.hasher.update(rem_data_elem); + } else { + // Put `rem_data_elem` back in `rem_data` + if (!in_pattern) { + rem_data = data[vec_elem_i..]; + } else { + rem_splat += 1; + } + } + rem_data_elem = undefined; // it is always updated below + + // Send through as much of the original vector as possible + var vec_n: usize = 0; + var vlimit = block_limit; + const vec_splat = while (rem_data[vec_n..].len != 1) { + vlimit = vlimit.subtract(rem_data[vec_n].len) orelse break 1; + vec_n += 1; + } else vec_splat: { + // For `pattern.len == 0`, the value of `vec_splat` does not matter. + const vec_splat = @intFromEnum(vlimit) / @max(1, pattern.len); + if (pattern.len != 0) assert(vec_splat <= rem_splat + 1); + vlimit = vlimit.subtract(pattern.len * vec_splat).?; + vec_n += 1; + break :vec_splat vec_splat; + }; + + const n = if (vec_n != 0) n: { + assert(@intFromEnum(block_limit) - @intFromEnum(vlimit) == + Writer.countSplat(rem_data[0..vec_n], vec_splat)); + break :n try h.bit_writer.output.writeSplat(rem_data[0..vec_n], vec_splat); + } else 0; // Still go into the case below to advance the vector + block_limit = block_limit.subtract(n).?; + var consumed: Io.Limit = .limited(n); + + while (rem_data.len != 1) { + const elem = rem_data[0]; + rem_data = rem_data[1..]; + consumed = consumed.subtract(elem.len) orelse { + h.hasher.update(elem[0..@intFromEnum(consumed)]); + rem_data_elem = elem[@intFromEnum(consumed)..]; + break; + }; + h.hasher.update(elem); + } else { + if (pattern.len == 0) { + // All of `data` has been consumed. However, the general + // case below does not work since it divides by zero. + assert(consumed == .nothing); + assert(block_limit == .nothing); + assert(rem_bytes == 0); + // Since `rem_bytes` and `block_limit` are zero, these won't be used. + rem_splat = undefined; + rem_data = undefined; + rem_data_elem = undefined; + break; + } + + const splatted = @intFromEnum(consumed) / pattern.len; + const partial = @intFromEnum(consumed) % pattern.len; + for (0..splatted) |_| h.hasher.update(pattern); + h.hasher.update(pattern[0..partial]); + + const taken_splat = splatted + 1; + if (rem_splat >= taken_splat) { + rem_splat -= taken_splat; + rem_data_elem = pattern[partial..]; + } else { + // All of `data` has been consumed. + assert(partial == 0); + assert(block_limit == .nothing); + assert(rem_bytes == 0); + // Since `rem_bytes` and `block_limit` are zero, these won't be used. + rem_data = undefined; + rem_data_elem = undefined; + rem_splat = undefined; + } + } + + if (block_limit == .nothing) break; + } + } + + if (rem_bytes > data_bytes) { + assert(rem_bytes - data_bytes == rem_data_elem.len); + assert(&rem_data_elem[0] == &w.buffer[total_bytes - rem_bytes]); + } + return w.consume(total_bytes - rem_bytes); + } + + fn flush(w: *Writer) Writer.Error!void { + defer w.* = .failing; + const h: *Huffman = @fieldParentPtr("writer", w); + try Huffman.rebaseInner(w, 0, w.buffer.len, true); + try h.bit_writer.output.rebase(0, 1); + h.bit_writer.byteAlign(); + try h.hasher.writeFooter(h.bit_writer.output); + } + + fn rebase(w: *Writer, preserve: usize, capacity: usize) Writer.Error!void { + errdefer w.* = .failing; + try Huffman.rebaseInner(w, preserve, capacity, false); + } + + fn rebaseInner(w: *Writer, preserve: usize, capacity: usize, eos: bool) Writer.Error!void { + const h: *Huffman = @fieldParentPtr("writer", w); + assert(preserve + capacity <= w.buffer.len); + if (eos) assert(capacity == w.buffer.len); + + const preserved = @min(w.end, preserve); + var remaining = w.buffer[0 .. w.end - preserved]; + while (remaining.len > max_tokens) { // not >= so there is always a block down below + const bytes = remaining[0..max_tokens]; + remaining = remaining[max_tokens..]; + try h.outputBytes(bytes, false); + } + + // eos check required for empty block + if (w.buffer.len - (remaining.len + preserved) < capacity or eos) { + const bytes = remaining; + remaining = &.{}; + try h.outputBytes(bytes, eos); + } + + _ = w.consume(w.end - preserved - remaining.len); + } + + fn outputBytes(h: *Huffman, bytes: []const u8, eos: bool) Writer.Error!void { + comptime assert(max_tokens != 65535); + assert(bytes.len <= max_tokens); + var freqs: [257]u16 = @splat(0); + freqs[256] = 1; + for (bytes) |b| freqs[b] += 1; + h.hasher.update(bytes); + + var codes_buf: CodesBuf = .init; + if (try h.outputHeader(&freqs, &codes_buf, @intCast(bytes.len), eos)) |table| { + for (bytes) |b| { + try h.bit_writer.write(table.codes[b], table.bits[b]); + } + try h.bit_writer.write(table.codes[256], table.bits[256]); + } else { + try h.bit_writer.output.writeAll(bytes); + } + } + + const CodesBuf = struct { + dyn_codes: [258]u16, + dyn_bits: [258]u4, + + pub const init: CodesBuf = .{ + .dyn_codes = @as([257]u16, undefined) ++ .{0}, + .dyn_bits = @as([257]u4, @splat(0)) ++ .{1}, + }; }; - enc.generate(freqs[0..], 7); - - try testing.expectEqual(@as(u32, 141), enc.bitLength(freqs[0..])); - - try testing.expectEqual(@as(usize, 3), enc.codes[0].len); - try testing.expectEqual(@as(usize, 6), enc.codes[1].len); - try testing.expectEqual(@as(usize, 6), enc.codes[2].len); - try testing.expectEqual(@as(usize, 5), enc.codes[3].len); - try testing.expectEqual(@as(usize, 3), enc.codes[4].len); - try testing.expectEqual(@as(usize, 2), enc.codes[5].len); - try testing.expectEqual(@as(usize, 2), enc.codes[6].len); - try testing.expectEqual(@as(usize, 6), enc.codes[7].len); - try testing.expectEqual(@as(usize, 0), enc.codes[8].len); - try testing.expectEqual(@as(usize, 0), enc.codes[9].len); - try testing.expectEqual(@as(usize, 0), enc.codes[10].len); - try testing.expectEqual(@as(usize, 0), enc.codes[11].len); - try testing.expectEqual(@as(usize, 0), enc.codes[12].len); - try testing.expectEqual(@as(usize, 0), enc.codes[13].len); - try testing.expectEqual(@as(usize, 0), enc.codes[14].len); - try testing.expectEqual(@as(usize, 0), enc.codes[15].len); - try testing.expectEqual(@as(usize, 6), enc.codes[16].len); - try testing.expectEqual(@as(usize, 5), enc.codes[17].len); - try testing.expectEqual(@as(usize, 3), enc.codes[18].len); - - try testing.expectEqual(@as(u16, 0x0), enc.codes[5].code); - try testing.expectEqual(@as(u16, 0x2), enc.codes[6].code); - try testing.expectEqual(@as(u16, 0x1), enc.codes[0].code); - try testing.expectEqual(@as(u16, 0x5), enc.codes[4].code); - try testing.expectEqual(@as(u16, 0x3), enc.codes[18].code); - try testing.expectEqual(@as(u16, 0x7), enc.codes[3].code); - try testing.expectEqual(@as(u16, 0x17), enc.codes[17].code); - try testing.expectEqual(@as(u16, 0x0f), enc.codes[1].code); - try testing.expectEqual(@as(u16, 0x2f), enc.codes[2].code); - try testing.expectEqual(@as(u16, 0x1f), enc.codes[7].code); - try testing.expectEqual(@as(u16, 0x3f), enc.codes[16].code); + + /// Returns null if the block is stored. + fn outputHeader( + h: *Huffman, + freqs: *const [257]u16, + buf: *CodesBuf, + bytes: u16, + eos: bool, + ) Writer.Error!?struct { + codes: *const [257]u16, + bits: *const [257]u4, + } { + assert(freqs[256] == 1); + const dyn_codes_bitsize, _ = huffman.build( + freqs, + buf.dyn_codes[0..257], + buf.dyn_bits[0..257], + 15, + true, + ); + + var clen_values: [258]u8 = undefined; + var clen_extra: [258]u8 = undefined; + var clen_freqs: [19]u16 = @splat(0); + const clen_len, const clen_extra_bitsize = buildClen( + &buf.dyn_bits, + &clen_values, + &clen_extra, + &clen_freqs, + ); + + var clen_codes: [19]u16 = undefined; + var clen_bits: [19]u4 = @splat(0); + const clen_codes_bitsize, _ = huffman.build( + &clen_freqs, + &clen_codes, + &clen_bits, + 7, + false, + ); + const hclen = clenHlen(clen_freqs); + + const dynamic_bitsize = @as(u32, 14) + + (4 + @as(u6, hclen)) * 3 + clen_codes_bitsize + clen_extra_bitsize + + dyn_codes_bitsize; + const fixed_bitsize = n: { + const freq7 = 1; // eos + var freq9: u16 = 0; + for (freqs[144..256]) |f| freq9 += f; + const freq8: u16 = bytes - freq9; + break :n @as(u32, freq7) * 7 + @as(u32, freq8) * 8 + @as(u32, freq9) * 9; + }; + const stored_bitsize = n: { + const stored_align_bits = -%(h.bit_writer.buffered_n +% 3); + break :n stored_align_bits + @as(u32, 32) + @as(u32, bytes) * 8; + }; + + //std.debug.print("@ {}{{{}}} ", .{ h.bit_writer.output.end, h.bit_writer.buffered_n }); + //std.debug.print("#{} -> s {} f {} d {}\n", .{ bytes, stored_bitsize, fixed_bitsize, dynamic_bitsize }); + + if (stored_bitsize <= @min(dynamic_bitsize, fixed_bitsize)) { + try h.bit_writer.write(BlockHeader.int(.{ .kind = .stored, .final = eos }), 3); + try h.bit_writer.output.rebase(0, 5); + h.bit_writer.byteAlign(); + h.bit_writer.output.writeInt(u16, bytes, .little) catch unreachable; + h.bit_writer.output.writeInt(u16, ~bytes, .little) catch unreachable; + return null; + } + + if (fixed_bitsize <= dynamic_bitsize) { + try h.bit_writer.write(BlockHeader.int(.{ .final = eos, .kind = .fixed }), 3); + return .{ + .codes = token.fixed_lit_codes[0..257], + .bits = token.fixed_lit_bits[0..257], + }; + } else { + try h.bit_writer.write(BlockHeader.Dynamic.int(.{ + .regular = .{ .final = eos, .kind = .dynamic }, + .hlit = 0, + .hdist = 0, + .hclen = hclen, + }), 17); + try h.bit_writer.writeClen( + hclen, + clen_values[0..clen_len], + clen_extra[0..clen_len], + clen_codes, + clen_bits, + ); + return .{ .codes = buf.dyn_codes[0..257], .bits = buf.dyn_bits[0..257] }; + } + } +}; + +test Huffman { + const fbufs = try testingFreqBufs(); + defer if (!builtin.fuzz) std.testing.allocator.destroy(fbufs); + try std.testing.fuzz(fbufs, testFuzzedHuffmanInput, .{}); +} + +/// This function is derived from `testFuzzedRawInput` with a few changes for fuzzing `Huffman`. +fn testFuzzedHuffmanInput(fbufs: *const [2][65536]u8, input: []const u8) !void { + var in: Io.Reader = .fixed(input); + const opts: packed struct(u19) { + container: PackedContainer, + buf_len: u17, + } = @bitCast(in.takeLeb128(u19) catch 0); + var flate_buf: [2 * 65536]u8 = undefined; + var flate_w: Writer = .fixed(&flate_buf); + var h_buf: [2 * 65536]u8 = undefined; + var h: Huffman = try .init( + &flate_w, + h_buf[0 .. opts.buf_len +% flate.max_window_len], + opts.container.val(), + ); + + var expected_hash: flate.Container.Hasher = .init(opts.container.val()); + var expected_size: u32 = 0; + var vecs: [32][]const u8 = undefined; + var vecs_n: usize = 0; + + while (in.seek != in.end) { + const VecInfo = packed struct(u55) { + output: bool, + /// If set, `data_len` and `splat` are reinterpreted as `capacity` + /// and `preserve_len` respectively and `output` is treated as set. + rebase: bool, + block_aligning_len: bool, + block_aligning_splat: bool, + data_off_hi: u8, + random_data: u1, + data_len: u16, + splat: u18, + /// This is less useful as each value is part of the same gradient 'step' + data_off_lo: u8, + }; + var vec_info: VecInfo = @bitCast(in.takeLeb128(u55) catch |e| switch (e) { + error.ReadFailed => unreachable, + error.Overflow, error.EndOfStream => 0, + }); + + { + const buffered = h.writer.buffered().len + countVec(vecs[0..vecs_n]); + const to_align = mem.alignForwardAnyAlign(usize, buffered, Huffman.max_tokens) - buffered; + assert((buffered + to_align) % Huffman.max_tokens == 0); + + if (vec_info.block_aligning_len) { + vec_info.data_len = @intCast(to_align); + } else if (vec_info.block_aligning_splat and vec_info.data_len != 0 and + to_align % vec_info.data_len == 0) + { + vec_info.splat = @divExact(@as(u18, @intCast(to_align)), vec_info.data_len) -% 1; + } + } + + var splat = if (vec_info.output and !vec_info.rebase) vec_info.splat +% 1 else 1; + add_vec: { + if (vec_info.rebase) break :add_vec; + if (expected_size +| math.mulWide(u18, vec_info.data_len, splat) > 4 * (1 << 16)) { + // Skip this vector to avoid this test taking too long. + splat = 1; + break :add_vec; + } + + const data_buf = &fbufs[vec_info.random_data]; + vecs[vecs_n] = data_buf[@min( + (@as(u16, vec_info.data_off_hi) << 8) | vec_info.data_off_lo, + data_buf.len - vec_info.data_len, + )..][0..vec_info.data_len]; + + for (0..splat) |_| expected_hash.update(vecs[vecs_n]); + expected_size += @as(u32, @intCast(vecs[vecs_n].len)) * splat; + vecs_n += 1; + } + + const want_drain = vecs_n == vecs.len or vec_info.output or vec_info.rebase or + in.seek == in.end; + if (want_drain and vecs_n != 0) { + var n = h.writer.buffered().len + Writer.countSplat(vecs[0..vecs_n], splat); + const oos = h.writer.writeSplatAll(vecs[0..vecs_n], splat) == error.WriteFailed; + n -= h.writer.buffered().len; + const block_lim = math.divCeil(usize, n, Huffman.max_tokens) catch unreachable; + const lim = flate_w.end + 6 * block_lim + n; // 6 since block header may span two bytes + if (flate_w.end > lim) return error.OverheadTooLarge; + if (oos) return; + + vecs_n = 0; + } else assert(splat == 1); + + if (vec_info.rebase) { + const old_end = flate_w.end; + var n = h.writer.buffered().len; + const oos = h.writer.rebase(vec_info.data_len, @min( + h.writer.buffer.len -| vec_info.data_len, + vec_info.splat, + )) == error.WriteFailed; + n -= h.writer.buffered().len; + const block_lim = math.divCeil(usize, n, Huffman.max_tokens) catch unreachable; + const lim = old_end + 6 * block_lim + n; // 6 since block header may span two bytes + if (flate_w.end > lim) return error.OverheadTooLarge; + if (oos) return; + } + } + + { + const old_end = flate_w.end; + const n = h.writer.buffered().len; + const oos = h.writer.flush() == error.WriteFailed; + assert(h.writer.buffered().len == 0); + const block_lim = @max(1, math.divCeil(usize, n, Huffman.max_tokens) catch unreachable); + const lim = old_end + 6 * block_lim + n + opts.container.val().footerSize(); + if (flate_w.end > lim) return error.OverheadTooLarge; + if (oos) return; + } + + try testingCheckDecompressedMatches(flate_w.buffered(), expected_size, expected_hash); } diff --git a/lib/std/compress/flate/Decompress.zig b/lib/std/compress/flate/Decompress.zig index 47570beab2..d9b6a82a63 100644 --- a/lib/std/compress/flate/Decompress.zig +++ b/lib/std/compress/flate/Decompress.zig @@ -7,11 +7,10 @@ const Reader = std.Io.Reader; const Container = flate.Container; const Decompress = @This(); -const Token = @import("Token.zig"); +const token = @import("token.zig"); input: *Reader, -next_bits: Bits, -remaining_bits: std.math.Log2Int(Bits), +consumed_bits: u3, reader: Reader, @@ -25,8 +24,6 @@ state: State, err: ?Error, -const Bits = usize; - const BlockType = enum(u2) { stored = 0, fixed = 1, @@ -39,6 +36,8 @@ const State = union(enum) { block_header, stored_block: u16, fixed_block, + fixed_block_literal: u8, + fixed_block_match: u16, dynamic_block, dynamic_block_literal: u8, dynamic_block_match: u16, @@ -87,8 +86,7 @@ pub fn init(input: *Reader, container: Container, buffer: []u8) Decompress { .end = 0, }, .input = input, - .next_bits = 0, - .remaining_bits = 0, + .consumed_bits = 0, .container_metadata = .init(container), .lit_dec = .{}, .dst_dec = .{}, @@ -183,27 +181,25 @@ fn streamIndirectInner(d: *Decompress) Reader.Error!usize { return 0; } -fn decodeLength(self: *Decompress, code: u8) !u16 { - if (code > 28) return error.InvalidCode; - const ml = Token.matchLength(code); - return if (ml.extra_bits == 0) // 0 - 5 extra bits - ml.base - else - ml.base + try self.takeBitsRuntime(ml.extra_bits); +fn decodeLength(self: *Decompress, code_int: u5) !u16 { + if (code_int > 28) return error.InvalidCode; + const l: token.LenCode = .fromInt(code_int); + const base = l.base(); + const extra = l.extraBits(); + return token.min_length + (base | try self.takeBits(extra)); } -fn decodeDistance(self: *Decompress, code: u8) !u16 { - if (code > 29) return error.InvalidCode; - const md = Token.matchDistance(code); - return if (md.extra_bits == 0) // 0 - 13 extra bits - md.base - else - md.base + try self.takeBitsRuntime(md.extra_bits); +fn decodeDistance(self: *Decompress, code_int: u5) !u16 { + if (code_int > 29) return error.InvalidCode; + const d: token.DistCode = .fromInt(code_int); + const base = d.base(); + const extra = d.extraBits(); + return token.min_distance + (base | try self.takeBits(extra)); } -// Decode code length symbol to code length. Writes decoded length into -// lens slice starting at position pos. Returns number of positions -// advanced. +/// Decode code length symbol to code length. Writes decoded length into +/// lens slice starting at position pos. Returns number of positions +/// advanced. fn dynamicCodeLength(self: *Decompress, code: u16, lens: []u4, pos: usize) !usize { if (pos >= lens.len) return error.InvalidDynamicBlockHeader; @@ -217,7 +213,7 @@ fn dynamicCodeLength(self: *Decompress, code: u16, lens: []u4, pos: usize) !usiz 16 => { // Copy the previous code length 3 - 6 times. // The next 2 bits indicate repeat length - const n: u8 = @as(u8, try self.takeBits(u2)) + 3; + const n: u8 = @as(u8, try self.takeIntBits(u2)) + 3; if (pos == 0 or pos + n > lens.len) return error.InvalidDynamicBlockHeader; for (0..n) |i| { @@ -226,17 +222,17 @@ fn dynamicCodeLength(self: *Decompress, code: u16, lens: []u4, pos: usize) !usiz return n; }, // Repeat a code length of 0 for 3 - 10 times. (3 bits of length) - 17 => return @as(u8, try self.takeBits(u3)) + 3, + 17 => return @as(u8, try self.takeIntBits(u3)) + 3, // Repeat a code length of 0 for 11 - 138 times (7 bits of length) - 18 => return @as(u8, try self.takeBits(u7)) + 11, + 18 => return @as(u8, try self.takeIntBits(u7)) + 11, else => return error.InvalidDynamicBlockHeader, } } fn decodeSymbol(self: *Decompress, decoder: anytype) !Symbol { // Maximum code len is 15 bits. - const sym = try decoder.find(@bitReverse(try self.peekBits(u15))); - try self.tossBits(sym.code_bits); + const sym = try decoder.find(@bitReverse(try self.peekIntBitsShort(u15))); + try self.tossBitsShort(sym.code_bits); return sym; } @@ -320,11 +316,11 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader .raw => continue :sw .block_header, }, .block_header => { - d.final_block = (try d.takeBits(u1)) != 0; - const block_type: BlockType = @enumFromInt(try d.takeBits(u2)); + d.final_block = (try d.takeIntBits(u1)) != 0; + const block_type: BlockType = @enumFromInt(try d.takeIntBits(u2)); switch (block_type) { .stored => { - d.alignBitsDiscarding(); + d.alignBitsForward(); // everything after this is byte aligned in stored block const len = try in.takeInt(u16, .little); const nlen = try in.takeInt(u16, .little); @@ -333,17 +329,17 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader }, .fixed => continue :sw .fixed_block, .dynamic => { - const hlit: u16 = @as(u16, try d.takeBits(u5)) + 257; // number of ll code entries present - 257 - const hdist: u16 = @as(u16, try d.takeBits(u5)) + 1; // number of distance code entries - 1 - const hclen: u8 = @as(u8, try d.takeBits(u4)) + 4; // hclen + 4 code lengths are encoded + const hlit: u16 = @as(u16, try d.takeIntBits(u5)) + 257; // number of ll code entries present - 257 + const hdist: u16 = @as(u16, try d.takeIntBits(u5)) + 1; // number of distance code entries - 1 + const hclen: u8 = @as(u8, try d.takeIntBits(u4)) + 4; // hclen + 4 code lengths are encoded if (hlit > 286 or hdist > 30) return error.InvalidDynamicBlockHeader; // lengths for code lengths var cl_lens: [19]u4 = @splat(0); - for (flate.HuffmanEncoder.codegen_order[0..hclen]) |i| { - cl_lens[i] = try d.takeBits(u3); + for (token.codegen_order[0..hclen]) |i| { + cl_lens[i] = try d.takeIntBits(u3); } var cl_dec: CodegenDecoder = .{}; try cl_dec.generate(&cl_lens); @@ -352,9 +348,9 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader var dec_lens: [286 + 30]u4 = @splat(0); var pos: usize = 0; while (pos < hlit + hdist) { - const peeked = @bitReverse(try d.peekBits(u7)); + const peeked = @bitReverse(try d.peekIntBitsShort(u7)); const sym = try cl_dec.find(peeked); - try d.tossBits(sym.code_bits); + try d.tossBitsShort(sym.code_bits); pos += try d.dynamicCodeLength(sym.symbol, &dec_lens, pos); } if (pos > hlit + hdist) { @@ -373,9 +369,12 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader } }, .stored_block => |remaining_len| { - const out = try w.writableSliceGreedyPreserve(flate.history_len, 1); + const out: []u8 = if (remaining != 0) + try w.writableSliceGreedyPreserve(flate.history_len, 1) + else + &.{}; var limited_out: [1][]u8 = .{limit.min(.limited(remaining_len)).slice(out)}; - const n = try d.input.readVec(&limited_out); + const n = try in.readVec(&limited_out); if (remaining_len - n == 0) { d.state = if (d.final_block) .protocol_footer else .block_header; } else { @@ -389,8 +388,14 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader const code = try d.readFixedCode(); switch (code) { 0...255 => { - try w.writeBytePreserve(flate.history_len, @intCast(code)); - remaining -= 1; + if (remaining != 0) { + @branchHint(.likely); + try w.writeBytePreserve(flate.history_len, @intCast(code)); + remaining -= 1; + } else { + d.state = .{ .fixed_block_literal = @intCast(code) }; + return @intFromEnum(limit) - remaining; + } }, 256 => { d.state = if (d.final_block) .protocol_footer else .block_header; @@ -400,9 +405,7 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader // Handles fixed block non literal (length) code. // Length code is followed by 5 bits of distance code. const length = try d.decodeLength(@intCast(code - 257)); - const distance = try d.decodeDistance(@bitReverse(try d.takeBits(u5))); - try writeMatch(w, length, distance); - remaining -= length; + continue :sw .{ .fixed_block_match = length }; }, else => return error.InvalidCode, } @@ -410,6 +413,24 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader d.state = .fixed_block; return @intFromEnum(limit) - remaining; }, + .fixed_block_literal => |symbol| { + assert(remaining != 0); + remaining -= 1; + try w.writeBytePreserve(flate.history_len, symbol); + continue :sw .fixed_block; + }, + .fixed_block_match => |length| { + if (remaining >= length) { + @branchHint(.likely); + const distance = try d.decodeDistance(@bitReverse(try d.takeIntBits(u5))); + try writeMatch(w, length, distance); + remaining -= length; + continue :sw .fixed_block; + } else { + d.state = .{ .fixed_block_match = length }; + return @intFromEnum(limit) - remaining; + } + }, .dynamic_block => { // In larger archives most blocks are usually dynamic, so // decompression performance depends on this logic. @@ -429,7 +450,7 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader }, .match => { // Decode match backreference <length, distance> - const length = try d.decodeLength(sym.symbol); + const length = try d.decodeLength(@intCast(sym.symbol)); continue :sw .{ .dynamic_block_match = length }; }, .end_of_block => { @@ -449,7 +470,7 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader @branchHint(.likely); remaining -= length; const dsm = try d.decodeSymbol(&d.dst_dec); - const distance = try d.decodeDistance(dsm.symbol); + const distance = try d.decodeDistance(@intCast(dsm.symbol)); try writeMatch(w, length, distance); continue :sw .dynamic_block; } else { @@ -458,23 +479,16 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader } }, .protocol_footer => { + d.alignBitsForward(); switch (d.container_metadata) { .gzip => |*gzip| { - d.alignBitsDiscarding(); - gzip.* = .{ - .crc = try in.takeInt(u32, .little), - .count = try in.takeInt(u32, .little), - }; + gzip.crc = try in.takeInt(u32, .little); + gzip.count = try in.takeInt(u32, .little); }, .zlib => |*zlib| { - d.alignBitsDiscarding(); - zlib.* = .{ - .adler = try in.takeInt(u32, .little), - }; - }, - .raw => { - d.alignBitsPreserving(); + zlib.adler = try in.takeInt(u32, .big); }, + .raw => {}, } d.state = .end; return @intFromEnum(limit) - remaining; @@ -487,10 +501,10 @@ fn streamInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader /// back from current write position, and `length` of bytes. fn writeMatch(w: *Writer, length: u16, distance: u16) !void { if (w.end < distance) return error.InvalidMatch; - if (length < Token.base_length) return error.InvalidMatch; - if (length > Token.max_length) return error.InvalidMatch; - if (distance < Token.min_distance) return error.InvalidMatch; - if (distance > Token.max_distance) return error.InvalidMatch; + if (length < token.min_length) return error.InvalidMatch; + if (length > token.max_length) return error.InvalidMatch; + if (distance < token.min_distance) return error.InvalidMatch; + if (distance > token.max_distance) return error.InvalidMatch; // This is not a @memmove; it intentionally repeats patterns caused by // iterating one byte at a time. @@ -500,137 +514,71 @@ fn writeMatch(w: *Writer, length: u16, distance: u16) !void { for (dest, src) |*d, s| d.* = s; } -fn takeBits(d: *Decompress, comptime U: type) !U { - const remaining_bits = d.remaining_bits; - const next_bits = d.next_bits; - if (remaining_bits >= @bitSizeOf(U)) { - const u: U = @truncate(next_bits); - d.next_bits = next_bits >> @bitSizeOf(U); - d.remaining_bits = remaining_bits - @bitSizeOf(U); - return u; - } - const in = d.input; - const next_int = in.takeInt(Bits, .little) catch |err| switch (err) { - error.ReadFailed => return error.ReadFailed, - error.EndOfStream => return takeBitsEnding(d, U), +fn peekBits(d: *Decompress, n: u4) !u16 { + const bits = d.input.peekInt(u32, .little) catch |e| return switch (e) { + error.ReadFailed => error.ReadFailed, + error.EndOfStream => d.peekBitsEnding(n), }; - const needed_bits = @bitSizeOf(U) - remaining_bits; - const u: U = @intCast(((next_int & ((@as(Bits, 1) << needed_bits) - 1)) << remaining_bits) | next_bits); - d.next_bits = next_int >> needed_bits; - d.remaining_bits = @intCast(@bitSizeOf(Bits) - @as(usize, needed_bits)); - return u; + const mask = @shlExact(@as(u16, 1), n) - 1; + return @intCast((bits >> d.consumed_bits) & mask); } -fn takeBitsEnding(d: *Decompress, comptime U: type) !U { - const remaining_bits = d.remaining_bits; - const next_bits = d.next_bits; - const in = d.input; - const n = in.bufferedLen(); - assert(n < @sizeOf(Bits)); - const needed_bits = @bitSizeOf(U) - remaining_bits; - if (n * 8 < needed_bits) return error.EndOfStream; - const next_int = in.takeVarInt(Bits, .little, n) catch |err| switch (err) { - error.ReadFailed => return error.ReadFailed, - error.EndOfStream => unreachable, - }; - const u: U = @intCast(((next_int & ((@as(Bits, 1) << needed_bits) - 1)) << remaining_bits) | next_bits); - d.next_bits = next_int >> needed_bits; - d.remaining_bits = @intCast(n * 8 - @as(usize, needed_bits)); - return u; +fn peekBitsEnding(d: *Decompress, n: u4) !u16 { + @branchHint(.unlikely); + + const left = d.input.buffered(); + if (left.len * 8 - d.consumed_bits < n) return error.EndOfStream; + const bits = std.mem.readVarInt(u32, left, .little); + const mask = @shlExact(@as(u16, 1), n) - 1; + return @intCast((bits >> d.consumed_bits) & mask); } -fn peekBits(d: *Decompress, comptime U: type) !U { - const remaining_bits = d.remaining_bits; - const next_bits = d.next_bits; - if (remaining_bits >= @bitSizeOf(U)) return @truncate(next_bits); - const in = d.input; - const next_int = in.peekInt(Bits, .little) catch |err| switch (err) { - error.ReadFailed => return error.ReadFailed, - error.EndOfStream => return peekBitsEnding(d, U), - }; - const needed_bits = @bitSizeOf(U) - remaining_bits; - return @intCast(((next_int & ((@as(Bits, 1) << needed_bits) - 1)) << remaining_bits) | next_bits); +/// Safe only after `peekBits` has been called with a greater or equal `n` value. +fn tossBits(d: *Decompress, n: u4) void { + d.input.toss((@as(u8, n) + d.consumed_bits) / 8); + d.consumed_bits +%= @truncate(n); } -fn peekBitsEnding(d: *Decompress, comptime U: type) !U { - const remaining_bits = d.remaining_bits; - const next_bits = d.next_bits; - const in = d.input; - var u: Bits = 0; - var remaining_needed_bits = @bitSizeOf(U) - remaining_bits; - var i: usize = 0; - while (remaining_needed_bits > 0) { - const peeked = in.peek(i + 1) catch |err| switch (err) { - error.ReadFailed => return error.ReadFailed, - error.EndOfStream => break, - }; - u |= @as(Bits, peeked[i]) << @intCast(i * 8); - remaining_needed_bits -|= 8; - i += 1; - } - if (remaining_bits == 0 and i == 0) return error.EndOfStream; - return @truncate((u << remaining_bits) | next_bits); -} - -fn tossBits(d: *Decompress, n: u4) !void { - const remaining_bits = d.remaining_bits; - const next_bits = d.next_bits; - if (remaining_bits >= n) { - d.next_bits = next_bits >> n; - d.remaining_bits = remaining_bits - n; - } else { - const in = d.input; - const next_int = in.takeInt(Bits, .little) catch |err| switch (err) { - error.ReadFailed => return error.ReadFailed, - error.EndOfStream => return tossBitsEnding(d, n), - }; - const needed_bits = n - remaining_bits; - d.next_bits = next_int >> needed_bits; - d.remaining_bits = @intCast(@bitSizeOf(Bits) - @as(usize, needed_bits)); - } +fn takeBits(d: *Decompress, n: u4) !u16 { + const bits = try d.peekBits(n); + d.tossBits(n); + return bits; } -fn tossBitsEnding(d: *Decompress, n: u4) !void { - const remaining_bits = d.remaining_bits; - const in = d.input; - const buffered_n = in.bufferedLen(); - if (buffered_n == 0) return error.EndOfStream; - assert(buffered_n < @sizeOf(Bits)); - const needed_bits = n - remaining_bits; - const next_int = in.takeVarInt(Bits, .little, buffered_n) catch |err| switch (err) { - error.ReadFailed => return error.ReadFailed, - error.EndOfStream => unreachable, +fn alignBitsForward(d: *Decompress) void { + d.input.toss(@intFromBool(d.consumed_bits != 0)); + d.consumed_bits = 0; +} + +fn peekBitsShort(d: *Decompress, n: u4) !u16 { + const bits = d.input.peekInt(u32, .little) catch |e| return switch (e) { + error.ReadFailed => error.ReadFailed, + error.EndOfStream => d.peekBitsShortEnding(n), }; - d.next_bits = next_int >> needed_bits; - d.remaining_bits = @intCast(@as(usize, buffered_n) * 8 -| @as(usize, needed_bits)); + const mask = @shlExact(@as(u16, 1), n) - 1; + return @intCast((bits >> d.consumed_bits) & mask); } -fn takeBitsRuntime(d: *Decompress, n: u4) !u16 { - const x = try peekBits(d, u16); - const mask: u16 = (@as(u16, 1) << n) - 1; - const u: u16 = @as(u16, @truncate(x)) & mask; - try tossBits(d, n); - return u; +fn peekBitsShortEnding(d: *Decompress, n: u4) !u16 { + @branchHint(.unlikely); + + const left = d.input.buffered(); + const bits = std.mem.readVarInt(u32, left, .little); + const mask = @shlExact(@as(u16, 1), n) - 1; + return @intCast((bits >> d.consumed_bits) & mask); } -fn alignBitsDiscarding(d: *Decompress) void { - const remaining_bits = d.remaining_bits; - if (remaining_bits == 0) return; - const n_bytes = remaining_bits / 8; - const in = d.input; - in.seek -= n_bytes; - d.remaining_bits = 0; - d.next_bits = 0; +fn tossBitsShort(d: *Decompress, n: u4) !void { + if (d.input.bufferedLen() * 8 + d.consumed_bits < n) return error.EndOfStream; + d.tossBits(n); } -fn alignBitsPreserving(d: *Decompress) void { - const remaining_bits: usize = d.remaining_bits; - if (remaining_bits == 0) return; - const n_bytes = (remaining_bits + 7) / 8; - const in = d.input; - in.seek -= n_bytes; - d.remaining_bits = 0; - d.next_bits = 0; +fn takeIntBits(d: *Decompress, T: type) !T { + return @intCast(try d.takeBits(@bitSizeOf(T))); +} + +fn peekIntBitsShort(d: *Decompress, T: type) !T { + return @intCast(try d.peekBitsShort(@bitSizeOf(T))); } /// Reads first 7 bits, and then maybe 1 or 2 more to get full 7,8 or 9 bit code. @@ -646,12 +594,12 @@ fn alignBitsPreserving(d: *Decompress) void { /// 280 - 287 8 11000000 through /// 11000111 fn readFixedCode(d: *Decompress) !u16 { - const code7 = @bitReverse(try d.takeBits(u7)); + const code7 = @bitReverse(try d.takeIntBits(u7)); return switch (code7) { 0...0b0010_111 => @as(u16, code7) + 256, - 0b0010_111 + 1...0b1011_111 => (@as(u16, code7) << 1) + @as(u16, try d.takeBits(u1)) - 0b0011_0000, - 0b1011_111 + 1...0b1100_011 => (@as(u16, code7 - 0b1100000) << 1) + try d.takeBits(u1) + 280, - else => (@as(u16, code7 - 0b1100_100) << 2) + @as(u16, @bitReverse(try d.takeBits(u2))) + 144, + 0b0010_111 + 1...0b1011_111 => (@as(u16, code7) << 1) + @as(u16, try d.takeIntBits(u1)) - 0b0011_0000, + 0b1011_111 + 1...0b1100_011 => (@as(u16, code7 - 0b1100000) << 1) + try d.takeIntBits(u1) + 280, + else => (@as(u16, code7 - 0b1100_100) << 2) + @as(u16, @bitReverse(try d.takeIntBits(u2))) + 144, }; } @@ -807,7 +755,7 @@ fn HuffmanDecoder( return self.findLinked(code, sym.next); } - inline fn findLinked(self: *Self, code: u16, start: u16) !Symbol { + fn findLinked(self: *Self, code: u16, start: u16) !Symbol { var pos = start; while (pos > 0) { const sym = self.symbols[pos]; @@ -898,57 +846,30 @@ test "init/find" { } test "encode/decode literals" { - var codes: [flate.HuffmanEncoder.max_num_frequencies]flate.HuffmanEncoder.Code = undefined; - for (1..286) |j| { // for all different number of codes - var enc: flate.HuffmanEncoder = .{ - .codes = &codes, - .freq_cache = undefined, - .bit_count = undefined, - .lns = undefined, - .lfs = undefined, - }; - // create frequencies - var freq = [_]u16{0} ** 286; - freq[256] = 1; // ensure we have end of block code - for (&freq, 1..) |*f, i| { - if (i % j == 0) - f.* = @intCast(i); - } - - // encoder from frequencies - enc.generate(&freq, 15); - - // get code_lens from encoder - var code_lens = [_]u4{0} ** 286; - for (code_lens, 0..) |_, i| { - code_lens[i] = @intCast(enc.codes[i].len); - } - // generate decoder from code lens - var dec: LiteralDecoder = .{}; - try dec.generate(&code_lens); - - // expect decoder code to match original encoder code - for (dec.symbols) |s| { - if (s.code_bits == 0) continue; - const c_code: u16 = @bitReverse(@as(u15, @intCast(s.code))); - const symbol: u16 = switch (s.kind) { - .literal => s.symbol, - .end_of_block => 256, - .match => @as(u16, s.symbol) + 257, - }; - - const c = enc.codes[symbol]; - try testing.expect(c.code == c_code); - } - - // find each symbol by code - for (enc.codes) |c| { - if (c.len == 0) continue; - - const s_code: u15 = @bitReverse(@as(u15, @intCast(c.code))); - const s = try dec.find(s_code); - try testing.expect(s.code == s_code); - try testing.expect(s.code_bits == c.len); + // Check that the example in RFC 1951 section 3.2.2 works (plus some zeroes) + const max_bits = 5; + var decoder: HuffmanDecoder(16, max_bits, 3) = .{}; + try decoder.generate(&.{ 3, 3, 3, 3, 0, 0, 3, 2, 4, 4 }); + + inline for (0.., .{ + @as(u3, 0b010), + @as(u3, 0b011), + @as(u3, 0b100), + @as(u3, 0b101), + @as(u0, 0), + @as(u0, 0), + @as(u3, 0b110), + @as(u2, 0b00), + @as(u4, 0b1110), + @as(u4, 0b1111), + }) |i, code| { + const bits = @bitSizeOf(@TypeOf(code)); + if (bits == 0) continue; + for (0..1 << (max_bits - bits)) |extra| { + const full = (@as(u16, code) << (max_bits - bits)) | @as(u16, @intCast(extra)); + const symbol = try decoder.find(full); + try testing.expectEqual(i, symbol.symbol); + try testing.expectEqual(bits, symbol.code_bits); } } } diff --git a/lib/std/compress/flate/HuffmanEncoder.zig b/lib/std/compress/flate/HuffmanEncoder.zig deleted file mode 100644 index 28a405b886..0000000000 --- a/lib/std/compress/flate/HuffmanEncoder.zig +++ /dev/null @@ -1,463 +0,0 @@ -const HuffmanEncoder = @This(); -const std = @import("std"); -const assert = std.debug.assert; -const testing = std.testing; - -codes: []Code, -// Reusable buffer with the longest possible frequency table. -freq_cache: [max_num_frequencies + 1]LiteralNode, -bit_count: [17]u32, -lns: []LiteralNode, // sorted by literal, stored to avoid repeated allocation in generate -lfs: []LiteralNode, // sorted by frequency, stored to avoid repeated allocation in generate - -pub const LiteralNode = struct { - literal: u16, - freq: u16, - - pub fn max() LiteralNode { - return .{ - .literal = std.math.maxInt(u16), - .freq = std.math.maxInt(u16), - }; - } -}; - -pub const Code = struct { - code: u16 = 0, - len: u16 = 0, -}; - -/// The odd order in which the codegen code sizes are written. -pub const codegen_order = [_]u32{ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; -/// The number of codegen codes. -pub const codegen_code_count = 19; - -/// The largest distance code. -pub const distance_code_count = 30; - -/// Maximum number of literals. -pub const max_num_lit = 286; - -/// Max number of frequencies used for a Huffman Code -/// Possible lengths are codegen_code_count (19), distance_code_count (30) and max_num_lit (286). -/// The largest of these is max_num_lit. -pub const max_num_frequencies = max_num_lit; - -/// Biggest block size for uncompressed block. -pub const max_store_block_size = 65535; -/// The special code used to mark the end of a block. -pub const end_block_marker = 256; - -/// Update this Huffman Code object to be the minimum code for the specified frequency count. -/// -/// freq An array of frequencies, in which frequency[i] gives the frequency of literal i. -/// max_bits The maximum number of bits to use for any literal. -pub fn generate(self: *HuffmanEncoder, freq: []u16, max_bits: u32) void { - var list = self.freq_cache[0 .. freq.len + 1]; - // Number of non-zero literals - var count: u32 = 0; - // Set list to be the set of all non-zero literals and their frequencies - for (freq, 0..) |f, i| { - if (f != 0) { - list[count] = LiteralNode{ .literal = @as(u16, @intCast(i)), .freq = f }; - count += 1; - } else { - list[count] = LiteralNode{ .literal = 0x00, .freq = 0 }; - self.codes[i].len = 0; - } - } - list[freq.len] = LiteralNode{ .literal = 0x00, .freq = 0 }; - - list = list[0..count]; - if (count <= 2) { - // Handle the small cases here, because they are awkward for the general case code. With - // two or fewer literals, everything has bit length 1. - for (list, 0..) |node, i| { - // "list" is in order of increasing literal value. - self.codes[node.literal] = .{ - .code = @intCast(i), - .len = 1, - }; - } - return; - } - self.lfs = list; - std.mem.sort(LiteralNode, self.lfs, {}, byFreq); - - // Get the number of literals for each bit count - const bit_count = self.bitCounts(list, max_bits); - // And do the assignment - self.assignEncodingAndSize(bit_count, list); -} - -pub fn bitLength(self: *HuffmanEncoder, freq: []u16) u32 { - var total: u32 = 0; - for (freq, 0..) |f, i| { - if (f != 0) { - total += @as(u32, @intCast(f)) * @as(u32, @intCast(self.codes[i].len)); - } - } - return total; -} - -/// Return the number of literals assigned to each bit size in the Huffman encoding -/// -/// This method is only called when list.len >= 3 -/// The cases of 0, 1, and 2 literals are handled by special case code. -/// -/// list: An array of the literals with non-zero frequencies -/// and their associated frequencies. The array is in order of increasing -/// frequency, and has as its last element a special element with frequency -/// `math.maxInt(i32)` -/// -/// max_bits: The maximum number of bits that should be used to encode any literal. -/// Must be less than 16. -/// -/// Returns an integer array in which array[i] indicates the number of literals -/// that should be encoded in i bits. -fn bitCounts(self: *HuffmanEncoder, list: []LiteralNode, max_bits_to_use: usize) []u32 { - var max_bits = max_bits_to_use; - const n = list.len; - const max_bits_limit = 16; - - assert(max_bits < max_bits_limit); - - // The tree can't have greater depth than n - 1, no matter what. This - // saves a little bit of work in some small cases - max_bits = @min(max_bits, n - 1); - - // Create information about each of the levels. - // A bogus "Level 0" whose sole purpose is so that - // level1.prev.needed == 0. This makes level1.next_pair_freq - // be a legitimate value that never gets chosen. - var levels: [max_bits_limit]LevelInfo = std.mem.zeroes([max_bits_limit]LevelInfo); - // leaf_counts[i] counts the number of literals at the left - // of ancestors of the rightmost node at level i. - // leaf_counts[i][j] is the number of literals at the left - // of the level j ancestor. - var leaf_counts: [max_bits_limit][max_bits_limit]u32 = @splat(@splat(0)); - - { - var level = @as(u32, 1); - while (level <= max_bits) : (level += 1) { - // For every level, the first two items are the first two characters. - // We initialize the levels as if we had already figured this out. - levels[level] = LevelInfo{ - .level = level, - .last_freq = list[1].freq, - .next_char_freq = list[2].freq, - .next_pair_freq = list[0].freq + list[1].freq, - .needed = 0, - }; - leaf_counts[level][level] = 2; - if (level == 1) { - levels[level].next_pair_freq = std.math.maxInt(i32); - } - } - } - - // We need a total of 2*n - 2 items at top level and have already generated 2. - levels[max_bits].needed = 2 * @as(u32, @intCast(n)) - 4; - - { - var level = max_bits; - while (true) { - var l = &levels[level]; - if (l.next_pair_freq == std.math.maxInt(i32) and l.next_char_freq == std.math.maxInt(i32)) { - // We've run out of both leaves and pairs. - // End all calculations for this level. - // To make sure we never come back to this level or any lower level, - // set next_pair_freq impossibly large. - l.needed = 0; - levels[level + 1].next_pair_freq = std.math.maxInt(i32); - level += 1; - continue; - } - - const prev_freq = l.last_freq; - if (l.next_char_freq < l.next_pair_freq) { - // The next item on this row is a leaf node. - const next = leaf_counts[level][level] + 1; - l.last_freq = l.next_char_freq; - // Lower leaf_counts are the same of the previous node. - leaf_counts[level][level] = next; - if (next >= list.len) { - l.next_char_freq = LiteralNode.max().freq; - } else { - l.next_char_freq = list[next].freq; - } - } else { - // The next item on this row is a pair from the previous row. - // next_pair_freq isn't valid until we generate two - // more values in the level below - l.last_freq = l.next_pair_freq; - // Take leaf counts from the lower level, except counts[level] remains the same. - @memcpy(leaf_counts[level][0..level], leaf_counts[level - 1][0..level]); - levels[l.level - 1].needed = 2; - } - - l.needed -= 1; - if (l.needed == 0) { - // We've done everything we need to do for this level. - // Continue calculating one level up. Fill in next_pair_freq - // of that level with the sum of the two nodes we've just calculated on - // this level. - if (l.level == max_bits) { - // All done! - break; - } - levels[l.level + 1].next_pair_freq = prev_freq + l.last_freq; - level += 1; - } else { - // If we stole from below, move down temporarily to replenish it. - while (levels[level - 1].needed > 0) { - level -= 1; - if (level == 0) { - break; - } - } - } - } - } - - // Somethings is wrong if at the end, the top level is null or hasn't used - // all of the leaves. - assert(leaf_counts[max_bits][max_bits] == n); - - var bit_count = self.bit_count[0 .. max_bits + 1]; - var bits: u32 = 1; - const counts = &leaf_counts[max_bits]; - { - var level = max_bits; - while (level > 0) : (level -= 1) { - // counts[level] gives the number of literals requiring at least "bits" - // bits to encode. - bit_count[bits] = counts[level] - counts[level - 1]; - bits += 1; - if (level == 0) { - break; - } - } - } - return bit_count; -} - -/// Look at the leaves and assign them a bit count and an encoding as specified -/// in RFC 1951 3.2.2 -fn assignEncodingAndSize(self: *HuffmanEncoder, bit_count: []u32, list_arg: []LiteralNode) void { - var code = @as(u16, 0); - var list = list_arg; - - for (bit_count, 0..) |bits, n| { - code <<= 1; - if (n == 0 or bits == 0) { - continue; - } - // The literals list[list.len-bits] .. list[list.len-bits] - // are encoded using "bits" bits, and get the values - // code, code + 1, .... The code values are - // assigned in literal order (not frequency order). - const chunk = list[list.len - @as(u32, @intCast(bits)) ..]; - - self.lns = chunk; - std.mem.sort(LiteralNode, self.lns, {}, byLiteral); - - for (chunk) |node| { - self.codes[node.literal] = .{ - .code = bitReverse(u16, code, @as(u5, @intCast(n))), - .len = @as(u16, @intCast(n)), - }; - code += 1; - } - list = list[0 .. list.len - @as(u32, @intCast(bits))]; - } -} - -fn byFreq(context: void, a: LiteralNode, b: LiteralNode) bool { - _ = context; - if (a.freq == b.freq) { - return a.literal < b.literal; - } - return a.freq < b.freq; -} - -/// Describes the state of the constructed tree for a given depth. -const LevelInfo = struct { - /// Our level. for better printing - level: u32, - /// The frequency of the last node at this level - last_freq: u32, - /// The frequency of the next character to add to this level - next_char_freq: u32, - /// The frequency of the next pair (from level below) to add to this level. - /// Only valid if the "needed" value of the next lower level is 0. - next_pair_freq: u32, - /// The number of chains remaining to generate for this level before moving - /// up to the next level - needed: u32, -}; - -fn byLiteral(context: void, a: LiteralNode, b: LiteralNode) bool { - _ = context; - return a.literal < b.literal; -} - -/// Reverse bit-by-bit a N-bit code. -fn bitReverse(comptime T: type, value: T, n: usize) T { - const r = @bitReverse(value); - return r >> @as(std.math.Log2Int(T), @intCast(@typeInfo(T).int.bits - n)); -} - -test bitReverse { - const ReverseBitsTest = struct { - in: u16, - bit_count: u5, - out: u16, - }; - - const reverse_bits_tests = [_]ReverseBitsTest{ - .{ .in = 1, .bit_count = 1, .out = 1 }, - .{ .in = 1, .bit_count = 2, .out = 2 }, - .{ .in = 1, .bit_count = 3, .out = 4 }, - .{ .in = 1, .bit_count = 4, .out = 8 }, - .{ .in = 1, .bit_count = 5, .out = 16 }, - .{ .in = 17, .bit_count = 5, .out = 17 }, - .{ .in = 257, .bit_count = 9, .out = 257 }, - .{ .in = 29, .bit_count = 5, .out = 23 }, - }; - - for (reverse_bits_tests) |h| { - const v = bitReverse(u16, h.in, h.bit_count); - try std.testing.expectEqual(h.out, v); - } -} - -/// Generates a HuffmanCode corresponding to the fixed literal table -pub fn fixedLiteralEncoder(codes: *[max_num_frequencies]Code) HuffmanEncoder { - var h: HuffmanEncoder = undefined; - h.codes = codes; - var ch: u16 = 0; - - while (ch < max_num_frequencies) : (ch += 1) { - var bits: u16 = undefined; - var size: u16 = undefined; - switch (ch) { - 0...143 => { - // size 8, 000110000 .. 10111111 - bits = ch + 48; - size = 8; - }, - 144...255 => { - // size 9, 110010000 .. 111111111 - bits = ch + 400 - 144; - size = 9; - }, - 256...279 => { - // size 7, 0000000 .. 0010111 - bits = ch - 256; - size = 7; - }, - else => { - // size 8, 11000000 .. 11000111 - bits = ch + 192 - 280; - size = 8; - }, - } - h.codes[ch] = .{ .code = bitReverse(u16, bits, @as(u5, @intCast(size))), .len = size }; - } - return h; -} - -pub fn fixedDistanceEncoder(codes: *[distance_code_count]Code) HuffmanEncoder { - var h: HuffmanEncoder = undefined; - h.codes = codes; - for (h.codes, 0..) |_, ch| { - h.codes[ch] = .{ .code = bitReverse(u16, @as(u16, @intCast(ch)), 5), .len = 5 }; - } - return h; -} - -pub fn huffmanDistanceEncoder(codes: *[distance_code_count]Code) HuffmanEncoder { - var distance_freq: [distance_code_count]u16 = @splat(0); - distance_freq[0] = 1; - // huff_distance is a static distance encoder used for huffman only encoding. - // It can be reused since we will not be encoding distance values. - var h: HuffmanEncoder = .{}; - h.codes = codes; - h.generate(distance_freq[0..], 15); - return h; -} - -test "generate a Huffman code for the fixed literal table specific to Deflate" { - var codes: [max_num_frequencies]Code = undefined; - const enc: HuffmanEncoder = .fixedLiteralEncoder(&codes); - for (enc.codes) |c| { - switch (c.len) { - 7 => { - const v = @bitReverse(@as(u7, @intCast(c.code))); - try testing.expect(v <= 0b0010111); - }, - 8 => { - const v = @bitReverse(@as(u8, @intCast(c.code))); - try testing.expect((v >= 0b000110000 and v <= 0b10111111) or - (v >= 0b11000000 and v <= 11000111)); - }, - 9 => { - const v = @bitReverse(@as(u9, @intCast(c.code))); - try testing.expect(v >= 0b110010000 and v <= 0b111111111); - }, - else => unreachable, - } - } -} - -test "generate a Huffman code for the 30 possible relative distances (LZ77 distances) of Deflate" { - var codes: [distance_code_count]Code = undefined; - const enc = fixedDistanceEncoder(&codes); - for (enc.codes) |c| { - const v = @bitReverse(@as(u5, @intCast(c.code))); - try testing.expect(v <= 29); - try testing.expect(c.len == 5); - } -} - -pub const fixed_codes = [_]u8{ - 0b00001100, 0b10001100, 0b01001100, 0b11001100, 0b00101100, 0b10101100, 0b01101100, 0b11101100, - 0b00011100, 0b10011100, 0b01011100, 0b11011100, 0b00111100, 0b10111100, 0b01111100, 0b11111100, - 0b00000010, 0b10000010, 0b01000010, 0b11000010, 0b00100010, 0b10100010, 0b01100010, 0b11100010, - 0b00010010, 0b10010010, 0b01010010, 0b11010010, 0b00110010, 0b10110010, 0b01110010, 0b11110010, - 0b00001010, 0b10001010, 0b01001010, 0b11001010, 0b00101010, 0b10101010, 0b01101010, 0b11101010, - 0b00011010, 0b10011010, 0b01011010, 0b11011010, 0b00111010, 0b10111010, 0b01111010, 0b11111010, - 0b00000110, 0b10000110, 0b01000110, 0b11000110, 0b00100110, 0b10100110, 0b01100110, 0b11100110, - 0b00010110, 0b10010110, 0b01010110, 0b11010110, 0b00110110, 0b10110110, 0b01110110, 0b11110110, - 0b00001110, 0b10001110, 0b01001110, 0b11001110, 0b00101110, 0b10101110, 0b01101110, 0b11101110, - 0b00011110, 0b10011110, 0b01011110, 0b11011110, 0b00111110, 0b10111110, 0b01111110, 0b11111110, - 0b00000001, 0b10000001, 0b01000001, 0b11000001, 0b00100001, 0b10100001, 0b01100001, 0b11100001, - 0b00010001, 0b10010001, 0b01010001, 0b11010001, 0b00110001, 0b10110001, 0b01110001, 0b11110001, - 0b00001001, 0b10001001, 0b01001001, 0b11001001, 0b00101001, 0b10101001, 0b01101001, 0b11101001, - 0b00011001, 0b10011001, 0b01011001, 0b11011001, 0b00111001, 0b10111001, 0b01111001, 0b11111001, - 0b00000101, 0b10000101, 0b01000101, 0b11000101, 0b00100101, 0b10100101, 0b01100101, 0b11100101, - 0b00010101, 0b10010101, 0b01010101, 0b11010101, 0b00110101, 0b10110101, 0b01110101, 0b11110101, - 0b00001101, 0b10001101, 0b01001101, 0b11001101, 0b00101101, 0b10101101, 0b01101101, 0b11101101, - 0b00011101, 0b10011101, 0b01011101, 0b11011101, 0b00111101, 0b10111101, 0b01111101, 0b11111101, - 0b00010011, 0b00100110, 0b01001110, 0b10011010, 0b00111100, 0b01100101, 0b11101010, 0b10110100, - 0b11101001, 0b00110011, 0b01100110, 0b11001110, 0b10011010, 0b00111101, 0b01100111, 0b11101110, - 0b10111100, 0b11111001, 0b00001011, 0b00010110, 0b00101110, 0b01011010, 0b10111100, 0b01100100, - 0b11101001, 0b10110010, 0b11100101, 0b00101011, 0b01010110, 0b10101110, 0b01011010, 0b10111101, - 0b01100110, 0b11101101, 0b10111010, 0b11110101, 0b00011011, 0b00110110, 0b01101110, 0b11011010, - 0b10111100, 0b01100101, 0b11101011, 0b10110110, 0b11101101, 0b00111011, 0b01110110, 0b11101110, - 0b11011010, 0b10111101, 0b01100111, 0b11101111, 0b10111110, 0b11111101, 0b00000111, 0b00001110, - 0b00011110, 0b00111010, 0b01111100, 0b11100100, 0b11101000, 0b10110001, 0b11100011, 0b00100111, - 0b01001110, 0b10011110, 0b00111010, 0b01111101, 0b11100110, 0b11101100, 0b10111001, 0b11110011, - 0b00010111, 0b00101110, 0b01011110, 0b10111010, 0b01111100, 0b11100101, 0b11101010, 0b10110101, - 0b11101011, 0b00110111, 0b01101110, 0b11011110, 0b10111010, 0b01111101, 0b11100111, 0b11101110, - 0b10111101, 0b11111011, 0b00001111, 0b00011110, 0b00111110, 0b01111010, 0b11111100, 0b11100100, - 0b11101001, 0b10110011, 0b11100111, 0b00101111, 0b01011110, 0b10111110, 0b01111010, 0b11111101, - 0b11100110, 0b11101101, 0b10111011, 0b11110111, 0b00011111, 0b00111110, 0b01111110, 0b11111010, - 0b11111100, 0b11100101, 0b11101011, 0b10110111, 0b11101111, 0b00111111, 0b01111110, 0b11111110, - 0b11111010, 0b11111101, 0b11100111, 0b11101111, 0b10111111, 0b11111111, 0b00000000, 0b00100000, - 0b00001000, 0b00001100, 0b10000001, 0b11000010, 0b11100000, 0b00001000, 0b00100100, 0b00001010, - 0b10001101, 0b11000001, 0b11100010, 0b11110000, 0b00000100, 0b00100010, 0b10001001, 0b01001100, - 0b10100001, 0b11010010, 0b11101000, 0b00000011, 0b10000011, 0b01000011, 0b11000011, 0b00100011, - 0b10100011, -}; diff --git a/lib/std/compress/flate/Lookup.zig b/lib/std/compress/flate/Lookup.zig deleted file mode 100644 index d1d93de50a..0000000000 --- a/lib/std/compress/flate/Lookup.zig +++ /dev/null @@ -1,130 +0,0 @@ -//! Lookup of the previous locations for the same 4 byte data. Works on hash of -//! 4 bytes data. Head contains position of the first match for each hash. Chain -//! points to the previous position of the same hash given the current location. - -const std = @import("std"); -const testing = std.testing; -const expect = testing.expect; -const flate = @import("../flate.zig"); -const Token = @import("Token.zig"); - -const Lookup = @This(); - -const prime4 = 0x9E3779B1; // 4 bytes prime number 2654435761 -const chain_len = 2 * flate.history_len; - -pub const bits = 15; -pub const len = 1 << bits; -pub const shift = 32 - bits; - -// Maps hash => first position -head: [len]u16 = [_]u16{0} ** len, -// Maps position => previous positions for the same hash value -chain: [chain_len]u16 = [_]u16{0} ** (chain_len), - -// Calculates hash of the 4 bytes from data. -// Inserts `pos` position of that hash in the lookup tables. -// Returns previous location with the same hash value. -pub fn add(self: *Lookup, data: []const u8, pos: u16) u16 { - if (data.len < 4) return 0; - const h = hash(data[0..4]); - return self.set(h, pos); -} - -// Returns previous location with the same hash value given the current -// position. -pub fn prev(self: *Lookup, pos: u16) u16 { - return self.chain[pos]; -} - -fn set(self: *Lookup, h: u32, pos: u16) u16 { - const p = self.head[h]; - self.head[h] = pos; - self.chain[pos] = p; - return p; -} - -// Slide all positions in head and chain for `n` -pub fn slide(self: *Lookup, n: u16) void { - for (&self.head) |*v| { - v.* -|= n; - } - var i: usize = 0; - while (i < n) : (i += 1) { - self.chain[i] = self.chain[i + n] -| n; - } -} - -// Add `len` 4 bytes hashes from `data` into lookup. -// Position of the first byte is `pos`. -pub fn bulkAdd(self: *Lookup, data: []const u8, length: u16, pos: u16) void { - if (length == 0 or data.len < Token.min_length) { - return; - } - var hb = - @as(u32, data[3]) | - @as(u32, data[2]) << 8 | - @as(u32, data[1]) << 16 | - @as(u32, data[0]) << 24; - _ = self.set(hashu(hb), pos); - - var i = pos; - for (4..@min(length + 3, data.len)) |j| { - hb = (hb << 8) | @as(u32, data[j]); - i += 1; - _ = self.set(hashu(hb), i); - } -} - -// Calculates hash of the first 4 bytes of `b`. -fn hash(b: *const [4]u8) u32 { - return hashu(@as(u32, b[3]) | - @as(u32, b[2]) << 8 | - @as(u32, b[1]) << 16 | - @as(u32, b[0]) << 24); -} - -fn hashu(v: u32) u32 { - return @intCast((v *% prime4) >> shift); -} - -test add { - const data = [_]u8{ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x01, 0x02, 0x03, - }; - - var h: Lookup = .{}; - for (data, 0..) |_, i| { - const p = h.add(data[i..], @intCast(i)); - if (i >= 8 and i < 24) { - try expect(p == i - 8); - } else { - try expect(p == 0); - } - } - - const v = Lookup.hash(data[2 .. 2 + 4]); - try expect(h.head[v] == 2 + 16); - try expect(h.chain[2 + 16] == 2 + 8); - try expect(h.chain[2 + 8] == 2); -} - -test bulkAdd { - const data = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; - - // one by one - var h: Lookup = .{}; - for (data, 0..) |_, i| { - _ = h.add(data[i..], @intCast(i)); - } - - // in bulk - var bh: Lookup = .{}; - bh.bulkAdd(data, data.len, 0); - - try testing.expectEqualSlices(u16, &h.head, &bh.head); - try testing.expectEqualSlices(u16, &h.chain, &bh.chain); -} diff --git a/lib/std/compress/flate/Token.zig b/lib/std/compress/flate/Token.zig deleted file mode 100644 index 1383047693..0000000000 --- a/lib/std/compress/flate/Token.zig +++ /dev/null @@ -1,333 +0,0 @@ -//! Token cat be literal: single byte of data or match; reference to the slice of -//! data in the same stream represented with <length, distance>. Where length -//! can be 3 - 258 bytes, and distance 1 - 32768 bytes. -//! -const std = @import("std"); -const assert = std.debug.assert; -const print = std.debug.print; -const expect = std.testing.expect; - -const Token = @This(); - -pub const Kind = enum(u1) { - literal, - match, -}; - -// Distance range 1 - 32768, stored in dist as 0 - 32767 (fits u15) -dist: u15 = 0, -// Length range 3 - 258, stored in len_lit as 0 - 255 (fits u8) -len_lit: u8 = 0, -kind: Kind = .literal, - -pub const base_length = 3; // smallest match length per the RFC section 3.2.5 -pub const min_length = 4; // min length used in this algorithm -pub const max_length = 258; - -pub const min_distance = 1; -pub const max_distance = std.compress.flate.history_len; - -pub fn literal(t: Token) u8 { - return t.len_lit; -} - -pub fn distance(t: Token) u16 { - return @as(u16, t.dist) + min_distance; -} - -pub fn length(t: Token) u16 { - return @as(u16, t.len_lit) + base_length; -} - -pub fn initLiteral(lit: u8) Token { - return .{ .kind = .literal, .len_lit = lit }; -} - -// distance range 1 - 32768, stored in dist as 0 - 32767 (u15) -// length range 3 - 258, stored in len_lit as 0 - 255 (u8) -pub fn initMatch(dist: u16, len: u16) Token { - assert(len >= min_length and len <= max_length); - assert(dist >= min_distance and dist <= max_distance); - return .{ - .kind = .match, - .dist = @intCast(dist - min_distance), - .len_lit = @intCast(len - base_length), - }; -} - -pub fn eql(t: Token, o: Token) bool { - return t.kind == o.kind and - t.dist == o.dist and - t.len_lit == o.len_lit; -} - -pub fn lengthCode(t: Token) u16 { - return match_lengths[match_lengths_index[t.len_lit]].code; -} - -pub fn lengthEncoding(t: Token) MatchLength { - var c = match_lengths[match_lengths_index[t.len_lit]]; - c.extra_length = t.len_lit - c.base_scaled; - return c; -} - -// Returns the distance code corresponding to a specific distance. -// Distance code is in range: 0 - 29. -pub fn distanceCode(t: Token) u8 { - var dist: u16 = t.dist; - if (dist < match_distances_index.len) { - return match_distances_index[dist]; - } - dist >>= 7; - if (dist < match_distances_index.len) { - return match_distances_index[dist] + 14; - } - dist >>= 7; - return match_distances_index[dist] + 28; -} - -pub fn distanceEncoding(t: Token) MatchDistance { - var c = match_distances[t.distanceCode()]; - c.extra_distance = t.dist - c.base_scaled; - return c; -} - -pub fn lengthExtraBits(code: u32) u8 { - return match_lengths[code - length_codes_start].extra_bits; -} - -pub fn matchLength(code: u8) MatchLength { - return match_lengths[code]; -} - -pub fn matchDistance(code: u8) MatchDistance { - return match_distances[code]; -} - -pub fn distanceExtraBits(code: u32) u8 { - return match_distances[code].extra_bits; -} - -pub fn show(t: Token) void { - if (t.kind == .literal) { - print("L('{c}'), ", .{t.literal()}); - } else { - print("M({d}, {d}), ", .{ t.distance(), t.length() }); - } -} - -// Returns index in match_lengths table for each length in range 0-255. -const match_lengths_index = [_]u8{ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, - 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, - 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, - 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, - 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, - 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, - 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, - 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, - 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, - 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, - 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, - 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, - 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, - 27, 27, 27, 27, 27, 28, -}; - -const MatchLength = struct { - code: u16, - base_scaled: u8, // base - 3, scaled to fit into u8 (0-255), same as lit_len field in Token. - base: u16, // 3-258 - extra_length: u8 = 0, - extra_bits: u4, -}; - -// match_lengths represents table from rfc (https://datatracker.ietf.org/doc/html/rfc1951#page-12) -// -// Extra Extra Extra -// Code Bits Length(s) Code Bits Lengths Code Bits Length(s) -// ---- ---- ------ ---- ---- ------- ---- ---- ------- -// 257 0 3 267 1 15,16 277 4 67-82 -// 258 0 4 268 1 17,18 278 4 83-98 -// 259 0 5 269 2 19-22 279 4 99-114 -// 260 0 6 270 2 23-26 280 4 115-130 -// 261 0 7 271 2 27-30 281 5 131-162 -// 262 0 8 272 2 31-34 282 5 163-194 -// 263 0 9 273 3 35-42 283 5 195-226 -// 264 0 10 274 3 43-50 284 5 227-257 -// 265 1 11,12 275 3 51-58 285 0 258 -// 266 1 13,14 276 3 59-66 -// -pub const length_codes_start = 257; - -const match_lengths = [_]MatchLength{ - .{ .extra_bits = 0, .base_scaled = 0, .base = 3, .code = 257 }, - .{ .extra_bits = 0, .base_scaled = 1, .base = 4, .code = 258 }, - .{ .extra_bits = 0, .base_scaled = 2, .base = 5, .code = 259 }, - .{ .extra_bits = 0, .base_scaled = 3, .base = 6, .code = 260 }, - .{ .extra_bits = 0, .base_scaled = 4, .base = 7, .code = 261 }, - .{ .extra_bits = 0, .base_scaled = 5, .base = 8, .code = 262 }, - .{ .extra_bits = 0, .base_scaled = 6, .base = 9, .code = 263 }, - .{ .extra_bits = 0, .base_scaled = 7, .base = 10, .code = 264 }, - .{ .extra_bits = 1, .base_scaled = 8, .base = 11, .code = 265 }, - .{ .extra_bits = 1, .base_scaled = 10, .base = 13, .code = 266 }, - .{ .extra_bits = 1, .base_scaled = 12, .base = 15, .code = 267 }, - .{ .extra_bits = 1, .base_scaled = 14, .base = 17, .code = 268 }, - .{ .extra_bits = 2, .base_scaled = 16, .base = 19, .code = 269 }, - .{ .extra_bits = 2, .base_scaled = 20, .base = 23, .code = 270 }, - .{ .extra_bits = 2, .base_scaled = 24, .base = 27, .code = 271 }, - .{ .extra_bits = 2, .base_scaled = 28, .base = 31, .code = 272 }, - .{ .extra_bits = 3, .base_scaled = 32, .base = 35, .code = 273 }, - .{ .extra_bits = 3, .base_scaled = 40, .base = 43, .code = 274 }, - .{ .extra_bits = 3, .base_scaled = 48, .base = 51, .code = 275 }, - .{ .extra_bits = 3, .base_scaled = 56, .base = 59, .code = 276 }, - .{ .extra_bits = 4, .base_scaled = 64, .base = 67, .code = 277 }, - .{ .extra_bits = 4, .base_scaled = 80, .base = 83, .code = 278 }, - .{ .extra_bits = 4, .base_scaled = 96, .base = 99, .code = 279 }, - .{ .extra_bits = 4, .base_scaled = 112, .base = 115, .code = 280 }, - .{ .extra_bits = 5, .base_scaled = 128, .base = 131, .code = 281 }, - .{ .extra_bits = 5, .base_scaled = 160, .base = 163, .code = 282 }, - .{ .extra_bits = 5, .base_scaled = 192, .base = 195, .code = 283 }, - .{ .extra_bits = 5, .base_scaled = 224, .base = 227, .code = 284 }, - .{ .extra_bits = 0, .base_scaled = 255, .base = 258, .code = 285 }, -}; - -// Used in distanceCode fn to get index in match_distance table for each distance in range 0-32767. -const match_distances_index = [_]u8{ - 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, - 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, - 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, - 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, - 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, - 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, - 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, - 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, - 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, - 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, -}; - -const MatchDistance = struct { - base_scaled: u16, // base - 1, same as Token dist field - base: u16, - extra_distance: u16 = 0, - code: u8, - extra_bits: u4, -}; - -// match_distances represents table from rfc (https://datatracker.ietf.org/doc/html/rfc1951#page-12) -// -// Extra Extra Extra -// Code Bits Dist Code Bits Dist Code Bits Distance -// ---- ---- ---- ---- ---- ------ ---- ---- -------- -// 0 0 1 10 4 33-48 20 9 1025-1536 -// 1 0 2 11 4 49-64 21 9 1537-2048 -// 2 0 3 12 5 65-96 22 10 2049-3072 -// 3 0 4 13 5 97-128 23 10 3073-4096 -// 4 1 5,6 14 6 129-192 24 11 4097-6144 -// 5 1 7,8 15 6 193-256 25 11 6145-8192 -// 6 2 9-12 16 7 257-384 26 12 8193-12288 -// 7 2 13-16 17 7 385-512 27 12 12289-16384 -// 8 3 17-24 18 8 513-768 28 13 16385-24576 -// 9 3 25-32 19 8 769-1024 29 13 24577-32768 -// -const match_distances = [_]MatchDistance{ - .{ .extra_bits = 0, .base_scaled = 0x0000, .code = 0, .base = 1 }, - .{ .extra_bits = 0, .base_scaled = 0x0001, .code = 1, .base = 2 }, - .{ .extra_bits = 0, .base_scaled = 0x0002, .code = 2, .base = 3 }, - .{ .extra_bits = 0, .base_scaled = 0x0003, .code = 3, .base = 4 }, - .{ .extra_bits = 1, .base_scaled = 0x0004, .code = 4, .base = 5 }, - .{ .extra_bits = 1, .base_scaled = 0x0006, .code = 5, .base = 7 }, - .{ .extra_bits = 2, .base_scaled = 0x0008, .code = 6, .base = 9 }, - .{ .extra_bits = 2, .base_scaled = 0x000c, .code = 7, .base = 13 }, - .{ .extra_bits = 3, .base_scaled = 0x0010, .code = 8, .base = 17 }, - .{ .extra_bits = 3, .base_scaled = 0x0018, .code = 9, .base = 25 }, - .{ .extra_bits = 4, .base_scaled = 0x0020, .code = 10, .base = 33 }, - .{ .extra_bits = 4, .base_scaled = 0x0030, .code = 11, .base = 49 }, - .{ .extra_bits = 5, .base_scaled = 0x0040, .code = 12, .base = 65 }, - .{ .extra_bits = 5, .base_scaled = 0x0060, .code = 13, .base = 97 }, - .{ .extra_bits = 6, .base_scaled = 0x0080, .code = 14, .base = 129 }, - .{ .extra_bits = 6, .base_scaled = 0x00c0, .code = 15, .base = 193 }, - .{ .extra_bits = 7, .base_scaled = 0x0100, .code = 16, .base = 257 }, - .{ .extra_bits = 7, .base_scaled = 0x0180, .code = 17, .base = 385 }, - .{ .extra_bits = 8, .base_scaled = 0x0200, .code = 18, .base = 513 }, - .{ .extra_bits = 8, .base_scaled = 0x0300, .code = 19, .base = 769 }, - .{ .extra_bits = 9, .base_scaled = 0x0400, .code = 20, .base = 1025 }, - .{ .extra_bits = 9, .base_scaled = 0x0600, .code = 21, .base = 1537 }, - .{ .extra_bits = 10, .base_scaled = 0x0800, .code = 22, .base = 2049 }, - .{ .extra_bits = 10, .base_scaled = 0x0c00, .code = 23, .base = 3073 }, - .{ .extra_bits = 11, .base_scaled = 0x1000, .code = 24, .base = 4097 }, - .{ .extra_bits = 11, .base_scaled = 0x1800, .code = 25, .base = 6145 }, - .{ .extra_bits = 12, .base_scaled = 0x2000, .code = 26, .base = 8193 }, - .{ .extra_bits = 12, .base_scaled = 0x3000, .code = 27, .base = 12289 }, - .{ .extra_bits = 13, .base_scaled = 0x4000, .code = 28, .base = 16385 }, - .{ .extra_bits = 13, .base_scaled = 0x6000, .code = 29, .base = 24577 }, -}; - -test "size" { - try expect(@sizeOf(Token) == 4); -} - -// testing table https://datatracker.ietf.org/doc/html/rfc1951#page-12 -test "MatchLength" { - var c = Token.initMatch(1, 4).lengthEncoding(); - try expect(c.code == 258); - try expect(c.extra_bits == 0); - try expect(c.extra_length == 0); - - c = Token.initMatch(1, 11).lengthEncoding(); - try expect(c.code == 265); - try expect(c.extra_bits == 1); - try expect(c.extra_length == 0); - - c = Token.initMatch(1, 12).lengthEncoding(); - try expect(c.code == 265); - try expect(c.extra_bits == 1); - try expect(c.extra_length == 1); - - c = Token.initMatch(1, 130).lengthEncoding(); - try expect(c.code == 280); - try expect(c.extra_bits == 4); - try expect(c.extra_length == 130 - 115); -} - -test "MatchDistance" { - var c = Token.initMatch(1, 4).distanceEncoding(); - try expect(c.code == 0); - try expect(c.extra_bits == 0); - try expect(c.extra_distance == 0); - - c = Token.initMatch(192, 4).distanceEncoding(); - try expect(c.code == 14); - try expect(c.extra_bits == 6); - try expect(c.extra_distance == 192 - 129); -} - -test "match_lengths" { - for (match_lengths, 0..) |ml, i| { - try expect(@as(u16, ml.base_scaled) + 3 == ml.base); - try expect(i + 257 == ml.code); - } - - for (match_distances, 0..) |mo, i| { - try expect(mo.base_scaled + 1 == mo.base); - try expect(i == mo.code); - } -} diff --git a/lib/std/compress/flate/token.zig b/lib/std/compress/flate/token.zig new file mode 100644 index 0000000000..3c0866896d --- /dev/null +++ b/lib/std/compress/flate/token.zig @@ -0,0 +1,286 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +pub const min_length = 3; +pub const max_length = 258; + +pub const min_distance = 1; +pub const max_distance = std.compress.flate.history_len; + +pub const codegen_order: [19]u8 = .{ + 16, 17, 18, + 0, 8, // + 7, 9, + 6, 10, + 5, 11, + 4, 12, + 3, 13, + 2, 14, + 1, 15, +}; + +pub const fixed_lit_codes = fixed_lit[0]; +pub const fixed_lit_bits = fixed_lit[1]; +const fixed_lit = blk: { + var codes: [286]u16 = undefined; + var bits: [286]u4 = undefined; + + for (0..143 + 1, 0b00110000..0b10111111 + 1) |i, v| { + codes[i] = @bitReverse(@as(u8, v)); + bits[i] = 8; + } + for (144..255 + 1, 0b110010000..0b111111111 + 1) |i, v| { + codes[i] = @bitReverse(@as(u9, v)); + bits[i] = 9; + } + for (256..279 + 1, 0b0000000..0b0010111 + 1) |i, v| { + codes[i] = @bitReverse(@as(u7, v)); + bits[i] = 7; + } + for (280..287 - 2 + 1, 0b11000000..0b11000111 - 2 + 1) |i, v| { + codes[i] = @bitReverse(@as(u8, v)); + bits[i] = 8; + } + break :blk .{ codes, bits }; +}; + +pub const fixed_dist_codes = fixed_dist[0]; +pub const fixed_dist_bits = fixed_dist[1]; +const fixed_dist = blk: { + var codes: [30]u16 = undefined; + const bits: [30]u4 = @splat(5); + + for (0..30) |i| { + codes[i] = @bitReverse(@as(u5, i)); + } + break :blk .{ codes, bits }; +}; + +// All paramters of codes can be derived matchematically, however some are faster to +// do via lookup table. For ReleaseSmall, we do all mathematically to save space. +pub const LenCode = if (builtin.mode != .ReleaseSmall) LookupLenCode else ShortLenCode; +pub const DistCode = if (builtin.mode != .ReleaseSmall) LookupDistCode else ShortDistCode; +const ShortLenCode = ShortCode(u8, u2, u3, true); +const ShortDistCode = ShortCode(u15, u1, u4, false); +/// For length and distance codes, they having this format. +/// +/// For example, length code 0b1101 (13 or literal 270) has high_bits=0b01 and high_log2=3 +/// and is 1_01_xx (2 extra bits). It is then offsetted by the min length of 3. +/// ^ bit 4 = 2 + high_log2 - 1 +/// +/// An exception is Length codes, where value 255 is assigned the special zero-bit code 28 or +/// literal 285. +fn ShortCode(Value: type, HighBits: type, HighLog2: type, len_special: bool) type { + return packed struct(u5) { + /// Bits preceding high bit or start if none + high_bits: HighBits, + /// High bit, 0 means none, otherwise it is at bit `x + high_log2 - 1` + high_log2: HighLog2, + + pub fn fromVal(v: Value) @This() { + if (len_special and v == 255) return .fromInt(28); + const high_bits = @bitSizeOf(HighBits) + 1; + const bits = @bitSizeOf(Value) - @clz(v); + if (bits <= high_bits) return @bitCast(@as(u5, @intCast(v))); + const high = v >> @intCast(bits - high_bits); + return .{ .high_bits = @truncate(high), .high_log2 = @intCast(bits - high_bits + 1) }; + } + + /// `@ctz(return) >= extraBits()` + pub fn base(c: @This()) Value { + if (len_special and c.toInt() == 28) return 255; + if (c.high_log2 <= 1) return @as(u5, @bitCast(c)); + const high_value = (@as(Value, @intFromBool(c.high_log2 != 0)) << @bitSizeOf(HighBits)) | c.high_bits; + const high_start = @as(std.math.Log2Int(Value), c.high_log2 - 1); + return @shlExact(high_value, high_start); + } + + const max_extra = @bitSizeOf(Value) - (1 + @bitSizeOf(HighLog2)); + pub fn extraBits(c: @This()) std.math.IntFittingRange(0, max_extra) { + if (len_special and c.toInt() == 28) return 0; + return @intCast(c.high_log2 -| 1); + } + + pub fn toInt(c: @This()) u5 { + return @bitCast(c); + } + + pub fn fromInt(x: u5) @This() { + return @bitCast(x); + } + }; +} + +const LookupLenCode = packed struct(u5) { + code: ShortLenCode, + + const code_table = table: { + var codes: [256]ShortLenCode = undefined; + for (0.., &codes) |v, *c| { + c.* = .fromVal(v); + } + break :table codes; + }; + + const base_table = table: { + var bases: [29]u8 = undefined; + for (0.., &bases) |c, *b| { + b.* = ShortLenCode.fromInt(c).base(); + } + break :table bases; + }; + + pub fn fromVal(v: u8) LookupLenCode { + return .{ .code = code_table[v] }; + } + + /// `@ctz(return) >= extraBits()` + pub fn base(c: LookupLenCode) u8 { + return base_table[c.toInt()]; + } + + pub fn extraBits(c: LookupLenCode) u3 { + return c.code.extraBits(); + } + + pub fn toInt(c: LookupLenCode) u5 { + return @bitCast(c); + } + + pub fn fromInt(x: u5) LookupLenCode { + return @bitCast(x); + } +}; + +const LookupDistCode = packed struct(u5) { + code: ShortDistCode, + + const base_table = table: { + var bases: [30]u15 = undefined; + for (0.., &bases) |c, *b| { + b.* = ShortDistCode.fromInt(c).base(); + } + break :table bases; + }; + + pub fn fromVal(v: u15) LookupDistCode { + return .{ .code = .fromVal(v) }; + } + + /// `@ctz(return) >= extraBits()` + pub fn base(c: LookupDistCode) u15 { + return base_table[c.toInt()]; + } + + pub fn extraBits(c: LookupDistCode) u4 { + return c.code.extraBits(); + } + + pub fn toInt(c: LookupDistCode) u5 { + return @bitCast(c); + } + + pub fn fromInt(x: u5) LookupDistCode { + return @bitCast(x); + } +}; + +test LenCode { + inline for ([_]type{ ShortLenCode, LookupLenCode }) |Code| { + // Check against the RFC 1951 table + for (0.., [_]struct { + base: u8, + extra_bits: u4, + }{ + // zig fmt: off + .{ .base = 3 - min_length, .extra_bits = 0 }, + .{ .base = 4 - min_length, .extra_bits = 0 }, + .{ .base = 5 - min_length, .extra_bits = 0 }, + .{ .base = 6 - min_length, .extra_bits = 0 }, + .{ .base = 7 - min_length, .extra_bits = 0 }, + .{ .base = 8 - min_length, .extra_bits = 0 }, + .{ .base = 9 - min_length, .extra_bits = 0 }, + .{ .base = 10 - min_length, .extra_bits = 0 }, + .{ .base = 11 - min_length, .extra_bits = 1 }, + .{ .base = 13 - min_length, .extra_bits = 1 }, + .{ .base = 15 - min_length, .extra_bits = 1 }, + .{ .base = 17 - min_length, .extra_bits = 1 }, + .{ .base = 19 - min_length, .extra_bits = 2 }, + .{ .base = 23 - min_length, .extra_bits = 2 }, + .{ .base = 27 - min_length, .extra_bits = 2 }, + .{ .base = 31 - min_length, .extra_bits = 2 }, + .{ .base = 35 - min_length, .extra_bits = 3 }, + .{ .base = 43 - min_length, .extra_bits = 3 }, + .{ .base = 51 - min_length, .extra_bits = 3 }, + .{ .base = 59 - min_length, .extra_bits = 3 }, + .{ .base = 67 - min_length, .extra_bits = 4 }, + .{ .base = 83 - min_length, .extra_bits = 4 }, + .{ .base = 99 - min_length, .extra_bits = 4 }, + .{ .base = 115 - min_length, .extra_bits = 4 }, + .{ .base = 131 - min_length, .extra_bits = 5 }, + .{ .base = 163 - min_length, .extra_bits = 5 }, + .{ .base = 195 - min_length, .extra_bits = 5 }, + .{ .base = 227 - min_length, .extra_bits = 5 }, + .{ .base = 258 - min_length, .extra_bits = 0 }, + }) |code, params| { + // zig fmt: on + const c: u5 = @intCast(code); + try std.testing.expectEqual(params.extra_bits, Code.extraBits(.fromInt(@intCast(c)))); + try std.testing.expectEqual(params.base, Code.base(.fromInt(@intCast(c)))); + for (params.base..params.base + @shlExact(@as(u16, 1), params.extra_bits) - + @intFromBool(c == 27)) |v| + { + try std.testing.expectEqual(c, Code.fromVal(@intCast(v)).toInt()); + } + } + } +} + +test DistCode { + inline for ([_]type{ ShortDistCode, LookupDistCode }) |Code| { + for (0.., [_]struct { + base: u15, + extra_bits: u4, + }{ + // zig fmt: off + .{ .base = 1 - min_distance, .extra_bits = 0 }, + .{ .base = 2 - min_distance, .extra_bits = 0 }, + .{ .base = 3 - min_distance, .extra_bits = 0 }, + .{ .base = 4 - min_distance, .extra_bits = 0 }, + .{ .base = 5 - min_distance, .extra_bits = 1 }, + .{ .base = 7 - min_distance, .extra_bits = 1 }, + .{ .base = 9 - min_distance, .extra_bits = 2 }, + .{ .base = 13 - min_distance, .extra_bits = 2 }, + .{ .base = 17 - min_distance, .extra_bits = 3 }, + .{ .base = 25 - min_distance, .extra_bits = 3 }, + .{ .base = 33 - min_distance, .extra_bits = 4 }, + .{ .base = 49 - min_distance, .extra_bits = 4 }, + .{ .base = 65 - min_distance, .extra_bits = 5 }, + .{ .base = 97 - min_distance, .extra_bits = 5 }, + .{ .base = 129 - min_distance, .extra_bits = 6 }, + .{ .base = 193 - min_distance, .extra_bits = 6 }, + .{ .base = 257 - min_distance, .extra_bits = 7 }, + .{ .base = 385 - min_distance, .extra_bits = 7 }, + .{ .base = 513 - min_distance, .extra_bits = 8 }, + .{ .base = 769 - min_distance, .extra_bits = 8 }, + .{ .base = 1025 - min_distance, .extra_bits = 9 }, + .{ .base = 1537 - min_distance, .extra_bits = 9 }, + .{ .base = 2049 - min_distance, .extra_bits = 10 }, + .{ .base = 3073 - min_distance, .extra_bits = 10 }, + .{ .base = 4097 - min_distance, .extra_bits = 11 }, + .{ .base = 6145 - min_distance, .extra_bits = 11 }, + .{ .base = 8193 - min_distance, .extra_bits = 12 }, + .{ .base = 12289 - min_distance, .extra_bits = 12 }, + .{ .base = 16385 - min_distance, .extra_bits = 13 }, + .{ .base = 24577 - min_distance, .extra_bits = 13 }, + }) |code, params| { + // zig fmt: on + const c: u5 = @intCast(code); + try std.testing.expectEqual(params.extra_bits, Code.extraBits(.fromInt(@intCast(c)))); + try std.testing.expectEqual(params.base, Code.base(.fromInt(@intCast(c)))); + for (params.base..params.base + @shlExact(@as(u16, 1), params.extra_bits)) |v| { + try std.testing.expectEqual(c, Code.fromVal(@intCast(v)).toInt()); + } + } + } +} diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 2fb056e60b..3807ec7d79 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -46,6 +46,8 @@ pub const aead = struct { pub const Aes256Ocb = @import("crypto/aes_ocb.zig").Aes256Ocb; }; + pub const aes_ccm = @import("crypto/aes_ccm.zig"); + pub const ascon = struct { pub const AsconAead128 = @import("crypto/ascon.zig").AsconAead128; }; @@ -89,6 +91,7 @@ pub const auth = struct { pub const Aegis256Mac_128 = variants.Aegis256Mac_128; }; pub const cmac = @import("crypto/cmac.zig"); + pub const cbc_mac = @import("crypto/cbc_mac.zig"); }; /// Core functions, that should rarely be used directly by applications. diff --git a/lib/std/crypto/25519/ed25519.zig b/lib/std/crypto/25519/ed25519.zig index c85039e6f2..0a8e0ee07d 100644 --- a/lib/std/crypto/25519/ed25519.zig +++ b/lib/std/crypto/25519/ed25519.zig @@ -669,7 +669,7 @@ test "test vectors" { Vec{ .msg_hex = "85e241a07d148b41e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec40", .public_key_hex = "442aad9f089ad9e14647b1ef9099a1ff4798d78589e66f28eca69c11f582a623", - .sig_hex = "8ce5b96c8f26d0ab6c47958c9e68b937104cd36e13c33566acd2fe8d38aa19427e71f98a4734e74f2f13f06f97c20d58cc3f54b8bd0d272f42b695dd7e89a8c2", + .sig_hex = "8ce5b96c8f26d0ab6c47958c9e68b937104cd36e13c33566acd2fe8d38aa19427e71f98a473474f2f13f06f97c20d58cc3f54b8bd0d272f42b695dd7e89a8c22", .expected = error.NonCanonical, // 7 - S >> L }, Vec{ diff --git a/lib/std/crypto/aes_ccm.zig b/lib/std/crypto/aes_ccm.zig new file mode 100644 index 0000000000..f56dc98f49 --- /dev/null +++ b/lib/std/crypto/aes_ccm.zig @@ -0,0 +1,876 @@ +//! AES-CCM (Counter with CBC-MAC) authenticated encryption. +//! AES-CCM* extends CCM to support encryption-only mode (tag_len=0). +//! +//! References: +//! - NIST SP 800-38C: https://csrc.nist.gov/publications/detail/sp/800-38c/final +//! - RFC 3610: https://datatracker.ietf.org/doc/html/rfc3610 + +const std = @import("std"); +const assert = std.debug.assert; +const crypto = std.crypto; +const mem = std.mem; +const modes = crypto.core.modes; +const AuthenticationError = crypto.errors.AuthenticationError; +const cbc_mac = @import("cbc_mac.zig"); + +/// AES-128-CCM* with no authentication (encryption-only, 13-byte nonce). +pub const Aes128Ccm0 = AesCcm(crypto.core.aes.Aes128, 0, 13); +/// AES-128-CCM with 8-byte authentication tag and 13-byte nonce. +pub const Aes128Ccm8 = AesCcm(crypto.core.aes.Aes128, 8, 13); +/// AES-128-CCM with 16-byte authentication tag and 13-byte nonce. +pub const Aes128Ccm16 = AesCcm(crypto.core.aes.Aes128, 16, 13); +/// AES-256-CCM* with no authentication (encryption-only, 13-byte nonce). +pub const Aes256Ccm0 = AesCcm(crypto.core.aes.Aes256, 0, 13); +/// AES-256-CCM with 8-byte authentication tag and 13-byte nonce. +pub const Aes256Ccm8 = AesCcm(crypto.core.aes.Aes256, 8, 13); +/// AES-256-CCM with 16-byte authentication tag and 13-byte nonce. +pub const Aes256Ccm16 = AesCcm(crypto.core.aes.Aes256, 16, 13); + +/// AES-CCM authenticated encryption (NIST SP 800-38C, RFC 3610). +/// CCM* mode extends CCM to support encryption-only mode when tag_len=0. +/// +/// `BlockCipher`: Block cipher type (must have 16-byte blocks). +/// `tag_len`: Authentication tag length in bytes (0, 4, 6, 8, 10, 12, 14, or 16). +/// When tag_len=0, CCM* provides encryption-only (no authentication). +/// `nonce_len`: Nonce length in bytes (7 to 13). +fn AesCcm(comptime BlockCipher: type, comptime tag_len: usize, comptime nonce_len: usize) type { + const block_length = BlockCipher.block.block_length; + + comptime { + assert(block_length == 16); // CCM requires 16-byte blocks + if (tag_len != 0 and (tag_len < 4 or tag_len > 16 or tag_len % 2 != 0)) { + @compileError("CCM tag_length must be 0, 4, 6, 8, 10, 12, 14, or 16 bytes"); + } + if (nonce_len < 7 or nonce_len > 13) { + @compileError("CCM nonce_length must be between 7 and 13 bytes"); + } + } + + const L = 15 - nonce_len; // Counter size in bytes (2 to 8) + + return struct { + pub const key_length = BlockCipher.key_bits / 8; + pub const tag_length = tag_len; + pub const nonce_length = nonce_len; + + /// `c`: Ciphertext output buffer (must be same length as m). + /// `tag`: Authentication tag output. + /// `m`: Plaintext message to encrypt. + /// `ad`: Associated data to authenticate. + /// `npub`: Public nonce (must be unique for each message with same key). + /// `key`: Encryption key. + pub fn encrypt( + c: []u8, + tag: *[tag_length]u8, + m: []const u8, + ad: []const u8, + npub: [nonce_length]u8, + key: [key_length]u8, + ) void { + assert(c.len == m.len); + + // Validate message length fits in L bytes + const max_msg_len: u64 = if (L >= 8) std.math.maxInt(u64) else (@as(u64, 1) << @as(u6, @intCast(L * 8))) - 1; + assert(m.len <= max_msg_len); + + const cipher_ctx = BlockCipher.initEnc(key); + + // CCM*: Skip authentication if tag_length is 0 (encryption-only mode) + if (tag_length > 0) { + // Compute CBC-MAC using the reusable CBC-MAC module + var mac_result: [block_length]u8 = undefined; + computeCbcMac(&mac_result, &key, m, ad, npub); + + // Construct counter block for tag encryption (counter = 0) + var ctr_block: [block_length]u8 = undefined; + formatCtrBlock(&ctr_block, npub, 0); + + // Encrypt the MAC tag + var s0: [block_length]u8 = undefined; + cipher_ctx.encrypt(&s0, &ctr_block); + for (tag, mac_result[0..tag_length], s0[0..tag_length]) |*t, mac_byte, s_byte| { + t.* = mac_byte ^ s_byte; + } + + crypto.secureZero(u8, &mac_result); + crypto.secureZero(u8, &s0); + } + + // Encrypt the plaintext using CTR mode (starting from counter = 1) + var ctr_block: [block_length]u8 = undefined; + formatCtrBlock(&ctr_block, npub, 1); + // CCM counter is in the last L bytes of the block + modes.ctrSlice(@TypeOf(cipher_ctx), cipher_ctx, c, m, ctr_block, .big, 1 + nonce_len, L); + } + + /// `m`: Plaintext output buffer (must be same length as c). + /// `c`: Ciphertext to decrypt. + /// `tag`: Authentication tag to verify. + /// `ad`: Associated data (must match encryption). + /// `npub`: Public nonce (must match encryption). + /// `key`: Private key. + /// + /// Asserts `c.len == m.len`. + /// Contents of `m` are undefined if an error is returned. + pub fn decrypt( + m: []u8, + c: []const u8, + tag: [tag_length]u8, + ad: []const u8, + npub: [nonce_length]u8, + key: [key_length]u8, + ) AuthenticationError!void { + assert(m.len == c.len); + + const cipher_ctx = BlockCipher.initEnc(key); + + // Decrypt the ciphertext using CTR mode (starting from counter = 1) + var ctr_block: [block_length]u8 = undefined; + formatCtrBlock(&ctr_block, npub, 1); + // CCM counter is in the last L bytes of the block + modes.ctrSlice(@TypeOf(cipher_ctx), cipher_ctx, m, c, ctr_block, .big, 1 + nonce_len, L); + + // CCM*: Skip authentication if tag_length is 0 (encryption-only mode) + if (tag_length > 0) { + // Compute CBC-MAC over decrypted plaintext + var mac_result: [block_length]u8 = undefined; + computeCbcMac(&mac_result, &key, m, ad, npub); + + // Decrypt the received tag + formatCtrBlock(&ctr_block, npub, 0); + var s0: [block_length]u8 = undefined; + cipher_ctx.encrypt(&s0, &ctr_block); + + // Reconstruct the expected MAC + var expected_mac: [tag_length]u8 = undefined; + for (&expected_mac, mac_result[0..tag_length], s0[0..tag_length]) |*e, mac_byte, s_byte| { + e.* = mac_byte ^ s_byte; + } + + // Constant-time tag comparison + const valid = crypto.timing_safe.eql([tag_length]u8, expected_mac, tag); + if (!valid) { + crypto.secureZero(u8, &expected_mac); + crypto.secureZero(u8, &mac_result); + crypto.secureZero(u8, &s0); + crypto.secureZero(u8, m); + return error.AuthenticationFailed; + } + + crypto.secureZero(u8, &expected_mac); + crypto.secureZero(u8, &mac_result); + crypto.secureZero(u8, &s0); + } + } + + /// Format the counter block for CTR mode + /// Counter block format: [flags | nonce | counter] + /// flags = L - 1 + fn formatCtrBlock(block: *[block_length]u8, npub: [nonce_length]u8, counter: u64) void { + @memset(block, 0); + block[0] = L - 1; // flags + @memcpy(block[1..][0..nonce_length], &npub); + // Counter goes in the last L bytes + const CounterInt = std.meta.Int(.unsigned, L * 8); + mem.writeInt(CounterInt, block[1 + nonce_length ..][0..L], @as(CounterInt, @intCast(counter)), .big); + } + + /// Compute CBC-MAC over the message and associated data. + /// CCM uses plain CBC-MAC, not CMAC (RFC 3610). + fn computeCbcMac(mac: *[block_length]u8, key: *const [key_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8) void { + const CbcMac = cbc_mac.CbcMac(BlockCipher); + var ctx = CbcMac.init(key); + + // Process B_0 block + var b0: [block_length]u8 = undefined; + formatB0Block(&b0, m.len, ad.len, npub); + ctx.update(&b0); + + // Process associated data if present + // RFC 3610: AD is (encoded_length || ad) padded to block boundary + if (ad.len > 0) { + // Encode and add associated data length + var ad_len_encoding: [10]u8 = undefined; + const ad_len_size = encodeAdLength(&ad_len_encoding, ad.len); + + // Process AD with padding to block boundary + ctx.update(ad_len_encoding[0..ad_len_size]); + ctx.update(ad); + + // Add zero padding to reach block boundary + const total_ad_size = ad_len_size + ad.len; + const remainder = total_ad_size % block_length; + if (remainder > 0) { + const padding = [_]u8{0} ** block_length; + ctx.update(padding[0 .. block_length - remainder]); + } + } + + // Process plaintext message + ctx.update(m); + + // Finalize MAC + ctx.final(mac); + } + + /// Format the B_0 block for CBC-MAC + /// B_0 format: [flags | nonce | message_length] + /// flags = 64*Adata + 8*M' + L' + /// where: Adata = (ad.len > 0), M' = (tag_length - 2)/2 if M>0 else 0, L' = L - 1 + /// CCM*: When tag_length=0, M' is encoded as 0 + fn formatB0Block(block: *[block_length]u8, msg_len: usize, ad_len: usize, npub: [nonce_length]u8) void { + @memset(block, 0); + + const Adata: u8 = if (ad_len > 0) 1 else 0; + const M_prime: u8 = if (tag_length > 0) @intCast((tag_length - 2) / 2) else 0; + const L_prime: u8 = L - 1; + + block[0] = (Adata << 6) | (M_prime << 3) | L_prime; + @memcpy(block[1..][0..nonce_length], &npub); + + // Encode message length in last L bytes + const LengthInt = std.meta.Int(.unsigned, L * 8); + mem.writeInt(LengthInt, block[1 + nonce_length ..][0..L], @as(LengthInt, @intCast(msg_len)), .big); + } + + /// Encode associated data length according to CCM specification + /// Returns the number of bytes written + fn encodeAdLength(buf: *[10]u8, ad_len: usize) usize { + if (ad_len < 65280) { // 2^16 - 2^8 + // Encode as 2 bytes + mem.writeInt(u16, buf[0..2], @as(u16, @intCast(ad_len)), .big); + return 2; + } else if (ad_len <= std.math.maxInt(u32)) { + // Encode as 0xff || 0xfe || 4 bytes + buf[0] = 0xff; + buf[1] = 0xfe; + mem.writeInt(u32, buf[2..6], @as(u32, @intCast(ad_len)), .big); + return 6; + } else { + // Encode as 0xff || 0xff || 8 bytes + buf[0] = 0xff; + buf[1] = 0xff; + mem.writeInt(u64, buf[2..10], @as(u64, @intCast(ad_len)), .big); + return 10; + } + } + }; +} + +// Tests + +const testing = std.testing; +const fmt = std.fmt; +const hexToBytes = fmt.hexToBytes; + +test "Aes256Ccm8 - Encrypt decrypt round-trip" { + const key: [32]u8 = [_]u8{0x42} ** 32; + const nonce: [13]u8 = [_]u8{0x11} ** 13; + const m = "Hello, World! This is a test message."; + var c: [m.len]u8 = undefined; + var m2: [m.len]u8 = undefined; + var tag: [Aes256Ccm8.tag_length]u8 = undefined; + + Aes256Ccm8.encrypt(&c, &tag, m, "", nonce, key); + + try Aes256Ccm8.decrypt(&m2, &c, tag, "", nonce, key); + + try testing.expectEqualSlices(u8, m[0..], m2[0..]); +} + +test "Aes256Ccm8 - Associated data" { + const key: [32]u8 = [_]u8{0x42} ** 32; + const nonce: [13]u8 = [_]u8{0x11} ** 13; + const m = "secret message"; + const ad = "additional authenticated data"; + var c: [m.len]u8 = undefined; + var m2: [m.len]u8 = undefined; + var tag: [Aes256Ccm8.tag_length]u8 = undefined; + + Aes256Ccm8.encrypt(&c, &tag, m, ad, nonce, key); + + try Aes256Ccm8.decrypt(&m2, &c, tag, ad, nonce, key); + try testing.expectEqualSlices(u8, m[0..], m2[0..]); + + var m3: [m.len]u8 = undefined; + const wrong_adata = "wrong data"; + const result = Aes256Ccm8.decrypt(&m3, &c, tag, wrong_adata, nonce, key); + try testing.expectError(error.AuthenticationFailed, result); +} + +test "Aes256Ccm8 - Wrong key" { + const key: [32]u8 = [_]u8{0x42} ** 32; + const wrong_key: [32]u8 = [_]u8{0x43} ** 32; + const nonce: [13]u8 = [_]u8{0x11} ** 13; + const m = "secret"; + var c: [m.len]u8 = undefined; + var m2: [m.len]u8 = undefined; + var tag: [Aes256Ccm8.tag_length]u8 = undefined; + + Aes256Ccm8.encrypt(&c, &tag, m, "", nonce, key); + + const result = Aes256Ccm8.decrypt(&m2, &c, tag, "", nonce, wrong_key); + try testing.expectError(error.AuthenticationFailed, result); +} + +test "Aes256Ccm8 - Corrupted ciphertext" { + const key: [32]u8 = [_]u8{0x42} ** 32; + const nonce: [13]u8 = [_]u8{0x11} ** 13; + const m = "secret message"; + var c: [m.len]u8 = undefined; + var m2: [m.len]u8 = undefined; + var tag: [Aes256Ccm8.tag_length]u8 = undefined; + + Aes256Ccm8.encrypt(&c, &tag, m, "", nonce, key); + + c[5] ^= 0xFF; + + const result = Aes256Ccm8.decrypt(&m2, &c, tag, "", nonce, key); + try testing.expectError(error.AuthenticationFailed, result); +} + +test "Aes256Ccm8 - Empty plaintext" { + const key: [32]u8 = [_]u8{0x42} ** 32; + const nonce: [13]u8 = [_]u8{0x11} ** 13; + const m = ""; + var c: [m.len]u8 = undefined; + var m2: [m.len]u8 = undefined; + var tag: [Aes256Ccm8.tag_length]u8 = undefined; + + Aes256Ccm8.encrypt(&c, &tag, m, "", nonce, key); + + try Aes256Ccm8.decrypt(&m2, &c, tag, "", nonce, key); + + try testing.expectEqual(@as(usize, 0), m2.len); +} + +test "Aes128Ccm8 - Basic functionality" { + const key: [16]u8 = [_]u8{0x42} ** 16; + const nonce: [13]u8 = [_]u8{0x11} ** 13; + const m = "Test AES-128-CCM"; + var c: [m.len]u8 = undefined; + var m2: [m.len]u8 = undefined; + var tag: [Aes128Ccm8.tag_length]u8 = undefined; + + Aes128Ccm8.encrypt(&c, &tag, m, "", nonce, key); + + try Aes128Ccm8.decrypt(&m2, &c, tag, "", nonce, key); + + try testing.expectEqualSlices(u8, m[0..], m2[0..]); +} + +test "Aes256Ccm16 - 16-byte tag" { + const key: [32]u8 = [_]u8{0x42} ** 32; + const nonce: [13]u8 = [_]u8{0x11} ** 13; + const m = "Test 16-byte tag"; + var c: [m.len]u8 = undefined; + var m2: [m.len]u8 = undefined; + var tag: [Aes256Ccm16.tag_length]u8 = undefined; + + Aes256Ccm16.encrypt(&c, &tag, m, "", nonce, key); + + try testing.expectEqual(@as(usize, 16), tag.len); + + try Aes256Ccm16.decrypt(&m2, &c, tag, "", nonce, key); + + try testing.expectEqualSlices(u8, m[0..], m2[0..]); +} + +test "Aes256Ccm8 - Edge case short nonce" { + const Aes256Ccm8_7 = AesCcm(crypto.core.aes.Aes256, 8, 7); + var key: [32]u8 = undefined; + _ = try hexToBytes(&key, "eda32f751456e33195f1f499cf2dc7c97ea127b6d488f211ccc5126fbb24afa6"); + var nonce: [7]u8 = undefined; + _ = try hexToBytes(&nonce, "a544218dadd3c1"); + var m: [1]u8 = undefined; + _ = try hexToBytes(&m, "00"); + + var c: [m.len]u8 = undefined; + var tag: [Aes256Ccm8_7.tag_length]u8 = undefined; + + Aes256Ccm8_7.encrypt(&c, &tag, &m, "", nonce, key); + + var m2: [c.len]u8 = undefined; + + try Aes256Ccm8_7.decrypt(&m2, &c, tag, "", nonce, key); + try testing.expectEqualSlices(u8, &m, &m2); +} + +test "Aes256Ccm8 - Edge case long nonce" { + var key: [32]u8 = undefined; + _ = try hexToBytes(&key, "e1b8a927a95efe94656677b692662000278b441c79e879dd5c0ddc758bdc9ee8"); + var nonce: [13]u8 = undefined; + _ = try hexToBytes(&nonce, "a544218dadd3c10583db49cf39"); + var m: [1]u8 = undefined; + _ = try hexToBytes(&m, "00"); + + var c: [m.len]u8 = undefined; + var tag: [Aes256Ccm8.tag_length]u8 = undefined; + + Aes256Ccm8.encrypt(&c, &tag, &m, "", nonce, key); + + var m2: [c.len]u8 = undefined; + + try Aes256Ccm8.decrypt(&m2, &c, tag, "", nonce, key); + try testing.expectEqualSlices(u8, &m, &m2); +} + +test "Aes256Ccm8 - With AAD and wrong AAD detection" { + var key: [32]u8 = undefined; + _ = try hexToBytes(&key, "8c5cf3457ff22228c39c051c4e05ed4093657eb303f859a9d4b0f8be0127d88a"); + var nonce: [13]u8 = undefined; + _ = try hexToBytes(&nonce, "a544218dadd3c10583db49cf39"); + var m: [1]u8 = undefined; + _ = try hexToBytes(&m, "00"); + var ad: [32]u8 = undefined; + _ = try hexToBytes(&ad, "3c0e2815d37d844f7ac240ba9d6e3a0b2a86f706e885959e09a1005e024f6907"); + + var c: [m.len]u8 = undefined; + var tag: [Aes256Ccm8.tag_length]u8 = undefined; + + Aes256Ccm8.encrypt(&c, &tag, &m, &ad, nonce, key); + + var m2: [c.len]u8 = undefined; + + try Aes256Ccm8.decrypt(&m2, &c, tag, &ad, nonce, key); + try testing.expectEqualSlices(u8, &m, &m2); + + var wrong_ad: [32]u8 = undefined; + _ = try hexToBytes(&wrong_ad, "0000000000000000000000000000000000000000000000000000000000000000"); + var m3: [c.len]u8 = undefined; + const result = Aes256Ccm8.decrypt(&m3, &c, tag, &wrong_ad, nonce, key); + try testing.expectError(error.AuthenticationFailed, result); +} + +test "Aes256Ccm8 - Multi-block payload" { + const Aes256Ccm8_12 = AesCcm(crypto.core.aes.Aes256, 8, 12); + + // Test with 32-byte payload (2 AES blocks) + var key: [32]u8 = undefined; + _ = try hexToBytes(&key, "af063639e66c284083c5cf72b70d8bc277f5978e80d9322d99f2fdc718cda569"); + var nonce: [12]u8 = undefined; + _ = try hexToBytes(&nonce, "a544218dadd3c10583db49cf"); + var m: [32]u8 = undefined; + _ = try hexToBytes(&m, "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + + // Encrypt + var c: [32]u8 = undefined; + var tag: [Aes256Ccm8_12.tag_length]u8 = undefined; + + Aes256Ccm8_12.encrypt(&c, &tag, &m, "", nonce, key); + + // Decrypt and verify + var m2: [32]u8 = undefined; + + try Aes256Ccm8_12.decrypt(&m2, &c, tag, "", nonce, key); + try testing.expectEqualSlices(u8, &m, &m2); +} + +test "Aes256Ccm8 - Multi-block with AAD" { + const Aes256Ccm8_12 = AesCcm(crypto.core.aes.Aes256, 8, 12); + + // Test with multi-block payload (3 AES blocks) and AAD + var key: [32]u8 = undefined; + _ = try hexToBytes(&key, "f7079dfa3b5c7b056347d7e437bcded683abd6e2c9e069d333284082cbb5d453"); + var nonce: [12]u8 = undefined; + _ = try hexToBytes(&nonce, "5b8e40746f6b98e00f1d13ff"); + + // 48-byte payload (3 AES blocks) + var m: [48]u8 = undefined; + _ = try hexToBytes(&m, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f"); + + // 16-byte AAD + var ad: [16]u8 = undefined; + _ = try hexToBytes(&ad, "000102030405060708090a0b0c0d0e0f"); + + // Encrypt + var c: [48]u8 = undefined; + var tag: [Aes256Ccm8_12.tag_length]u8 = undefined; + + Aes256Ccm8_12.encrypt(&c, &tag, &m, &ad, nonce, key); + + // Decrypt and verify + var m2: [48]u8 = undefined; + + try Aes256Ccm8_12.decrypt(&m2, &c, tag, &ad, nonce, key); + try testing.expectEqualSlices(u8, &m, &m2); +} + +test "Aes256Ccm8 - Minimum nonce length" { + const Aes256Ccm8_7 = AesCcm(crypto.core.aes.Aes256, 8, 7); + + // Test with 7-byte nonce (minimum allowed by CCM spec) + var key: [32]u8 = undefined; + _ = try hexToBytes(&key, "404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"); + var nonce: [7]u8 = undefined; + _ = try hexToBytes(&nonce, "10111213141516"); + const m = "Test message with minimum nonce length"; + + // Encrypt + var c: [m.len]u8 = undefined; + var tag: [Aes256Ccm8_7.tag_length]u8 = undefined; + + Aes256Ccm8_7.encrypt(&c, &tag, m, "", nonce, key); + + // Decrypt and verify + var m2: [m.len]u8 = undefined; + + try Aes256Ccm8_7.decrypt(&m2, &c, tag, "", nonce, key); + try testing.expectEqualSlices(u8, m[0..], m2[0..]); +} + +test "Aes256Ccm8 - Maximum nonce length" { + // Test with 13-byte nonce (maximum allowed by CCM spec) + var key: [32]u8 = undefined; + _ = try hexToBytes(&key, "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f"); + var nonce: [13]u8 = undefined; + _ = try hexToBytes(&nonce, "101112131415161718191a1b1c"); + const m = "Test message with maximum nonce length"; + + // Encrypt + var c: [m.len]u8 = undefined; + var tag: [Aes256Ccm8.tag_length]u8 = undefined; + + Aes256Ccm8.encrypt(&c, &tag, m, "", nonce, key); + + // Decrypt and verify + var m2: [m.len]u8 = undefined; + + try Aes256Ccm8.decrypt(&m2, &c, tag, "", nonce, key); + try testing.expectEqualSlices(u8, m[0..], m2[0..]); +} + +// RFC 3610 test vectors + +test "Aes128Ccm8 - RFC 3610 Packet Vector #1" { + const Aes128Ccm8_13 = AesCcm(crypto.core.aes.Aes128, 8, 13); + + // RFC 3610 Appendix A, Packet Vector #1 + var key: [16]u8 = undefined; + _ = try hexToBytes(&key, "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF"); + var nonce: [13]u8 = undefined; + _ = try hexToBytes(&nonce, "00000003020100A0A1A2A3A4A5"); + var ad: [8]u8 = undefined; + _ = try hexToBytes(&ad, "0001020304050607"); + var plaintext: [23]u8 = undefined; + _ = try hexToBytes(&plaintext, "08090A0B0C0D0E0F101112131415161718191A1B1C1D1E"); + + // Expected ciphertext and tag from RFC + var expected_ciphertext: [23]u8 = undefined; + _ = try hexToBytes(&expected_ciphertext, "588C979A61C663D2F066D0C2C0F989806D5F6B61DAC384"); + var expected_tag: [8]u8 = undefined; + _ = try hexToBytes(&expected_tag, "17E8D12CFDF926E0"); + + // Encrypt + var c: [plaintext.len]u8 = undefined; + var tag: [Aes128Ccm8_13.tag_length]u8 = undefined; + + Aes128Ccm8_13.encrypt(&c, &tag, &plaintext, &ad, nonce, key); + + // Verify ciphertext matches RFC expected output + try testing.expectEqualSlices(u8, &expected_ciphertext, &c); + + // Verify tag matches RFC expected output + try testing.expectEqualSlices(u8, &expected_tag, &tag); + + // Decrypt and verify round-trip + var m: [plaintext.len]u8 = undefined; + try Aes128Ccm8_13.decrypt(&m, &c, tag, &ad, nonce, key); + try testing.expectEqualSlices(u8, &plaintext, &m); +} + +test "Aes128Ccm8 - RFC 3610 Packet Vector #2" { + const Aes128Ccm8_13 = AesCcm(crypto.core.aes.Aes128, 8, 13); + + // RFC 3610 Appendix A, Packet Vector #2 (8-byte tag, M=8) + var key: [16]u8 = undefined; + _ = try hexToBytes(&key, "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF"); + var nonce: [13]u8 = undefined; + _ = try hexToBytes(&nonce, "00000004030201A0A1A2A3A4A5"); + var ad: [8]u8 = undefined; + _ = try hexToBytes(&ad, "0001020304050607"); + var plaintext: [24]u8 = undefined; + _ = try hexToBytes(&plaintext, "08090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); + + // Expected ciphertext and tag from RFC (from total packet: header + ciphertext + tag) + var expected_ciphertext: [24]u8 = undefined; + _ = try hexToBytes(&expected_ciphertext, "72C91A36E135F8CF291CA894085C87E3CC15C439C9E43A3B"); + var expected_tag: [8]u8 = undefined; + _ = try hexToBytes(&expected_tag, "A091D56E10400916"); + + // Encrypt + var c: [plaintext.len]u8 = undefined; + var tag: [Aes128Ccm8_13.tag_length]u8 = undefined; + + Aes128Ccm8_13.encrypt(&c, &tag, &plaintext, &ad, nonce, key); + + // Verify ciphertext matches RFC expected output + try testing.expectEqualSlices(u8, &expected_ciphertext, &c); + + // Verify tag matches RFC expected output + try testing.expectEqualSlices(u8, &expected_tag, &tag); + + // Decrypt and verify round-trip + var m: [plaintext.len]u8 = undefined; + try Aes128Ccm8_13.decrypt(&m, &c, tag, &ad, nonce, key); + try testing.expectEqualSlices(u8, &plaintext, &m); +} + +test "Aes128Ccm8 - RFC 3610 Packet Vector #3" { + const Aes128Ccm8_13 = AesCcm(crypto.core.aes.Aes128, 8, 13); + + // RFC 3610 Appendix A, Packet Vector #3 (8-byte tag, 25-byte payload) + var key: [16]u8 = undefined; + _ = try hexToBytes(&key, "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF"); + var nonce: [13]u8 = undefined; + _ = try hexToBytes(&nonce, "00000005040302A0A1A2A3A4A5"); + var ad: [8]u8 = undefined; + _ = try hexToBytes(&ad, "0001020304050607"); + var plaintext: [25]u8 = undefined; + _ = try hexToBytes(&plaintext, "08090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20"); + + // Expected ciphertext and tag from RFC + var expected_ciphertext: [25]u8 = undefined; + _ = try hexToBytes(&expected_ciphertext, "51B1E5F44A197D1DA46B0F8E2D282AE871E838BB64DA859657"); + var expected_tag: [8]u8 = undefined; + _ = try hexToBytes(&expected_tag, "4ADAA76FBD9FB0C5"); + + // Encrypt + var c: [plaintext.len]u8 = undefined; + var tag: [Aes128Ccm8_13.tag_length]u8 = undefined; + + Aes128Ccm8_13.encrypt(&c, &tag, &plaintext, &ad, nonce, key); + + // Verify ciphertext matches RFC expected output + try testing.expectEqualSlices(u8, &expected_ciphertext, &c); + + // Verify tag matches RFC expected output + try testing.expectEqualSlices(u8, &expected_tag, &tag); + + // Decrypt and verify round-trip + var m: [plaintext.len]u8 = undefined; + try Aes128Ccm8_13.decrypt(&m, &c, tag, &ad, nonce, key); + try testing.expectEqualSlices(u8, &plaintext, &m); +} + +// NIST SP 800-38C test vectors + +test "Aes128Ccm4 - NIST SP 800-38C Example 1" { + const Aes128Ccm4_7 = AesCcm(crypto.core.aes.Aes128, 4, 7); + + // Example 1 (C.1): Klen=128, Tlen=32, Nlen=56, Alen=64, Plen=32 + var key: [16]u8 = undefined; + _ = try hexToBytes(&key, "404142434445464748494a4b4c4d4e4f"); + var nonce: [7]u8 = undefined; + _ = try hexToBytes(&nonce, "10111213141516"); + var ad: [8]u8 = undefined; + _ = try hexToBytes(&ad, "0001020304050607"); + var plaintext: [4]u8 = undefined; + _ = try hexToBytes(&plaintext, "20212223"); + + // Expected ciphertext and tag from NIST + var expected_ciphertext: [4]u8 = undefined; + _ = try hexToBytes(&expected_ciphertext, "7162015b"); + var expected_tag: [4]u8 = undefined; + _ = try hexToBytes(&expected_tag, "4dac255d"); + + // Encrypt + var c: [plaintext.len]u8 = undefined; + var tag: [Aes128Ccm4_7.tag_length]u8 = undefined; + + Aes128Ccm4_7.encrypt(&c, &tag, &plaintext, &ad, nonce, key); + + // Verify ciphertext matches NIST expected output + try testing.expectEqualSlices(u8, &expected_ciphertext, &c); + + // Verify tag matches NIST expected output + try testing.expectEqualSlices(u8, &expected_tag, &tag); + + // Decrypt and verify round-trip + var m: [plaintext.len]u8 = undefined; + try Aes128Ccm4_7.decrypt(&m, &c, tag, &ad, nonce, key); + try testing.expectEqualSlices(u8, &plaintext, &m); +} + +test "Aes128Ccm6 - NIST SP 800-38C Example 2" { + const Aes128Ccm6_8 = AesCcm(crypto.core.aes.Aes128, 6, 8); + + // Example 2 (C.2): Klen=128, Tlen=48, Nlen=64, Alen=128, Plen=128 + var key: [16]u8 = undefined; + _ = try hexToBytes(&key, "404142434445464748494a4b4c4d4e4f"); + var nonce: [8]u8 = undefined; + _ = try hexToBytes(&nonce, "1011121314151617"); + var ad: [16]u8 = undefined; + _ = try hexToBytes(&ad, "000102030405060708090a0b0c0d0e0f"); + var plaintext: [16]u8 = undefined; + _ = try hexToBytes(&plaintext, "202122232425262728292a2b2c2d2e2f"); + + // Expected ciphertext and tag from NIST + var expected_ciphertext: [16]u8 = undefined; + _ = try hexToBytes(&expected_ciphertext, "d2a1f0e051ea5f62081a7792073d593d"); + var expected_tag: [6]u8 = undefined; + _ = try hexToBytes(&expected_tag, "1fc64fbfaccd"); + + // Encrypt + var c: [plaintext.len]u8 = undefined; + var tag: [Aes128Ccm6_8.tag_length]u8 = undefined; + + Aes128Ccm6_8.encrypt(&c, &tag, &plaintext, &ad, nonce, key); + + // Verify ciphertext matches NIST expected output + try testing.expectEqualSlices(u8, &expected_ciphertext, &c); + + // Verify tag matches NIST expected output + try testing.expectEqualSlices(u8, &expected_tag, &tag); + + // Decrypt and verify round-trip + var m: [plaintext.len]u8 = undefined; + try Aes128Ccm6_8.decrypt(&m, &c, tag, &ad, nonce, key); + try testing.expectEqualSlices(u8, &plaintext, &m); +} + +test "Aes128Ccm8 - NIST SP 800-38C Example 3" { + const Aes128Ccm8_12 = AesCcm(crypto.core.aes.Aes128, 8, 12); + + // Example 3 (C.3): Klen=128, Tlen=64, Nlen=96, Alen=160, Plen=192 + var key: [16]u8 = undefined; + _ = try hexToBytes(&key, "404142434445464748494a4b4c4d4e4f"); + var nonce: [12]u8 = undefined; + _ = try hexToBytes(&nonce, "101112131415161718191a1b"); + var ad: [20]u8 = undefined; + _ = try hexToBytes(&ad, "000102030405060708090a0b0c0d0e0f10111213"); + var plaintext: [24]u8 = undefined; + _ = try hexToBytes(&plaintext, "202122232425262728292a2b2c2d2e2f3031323334353637"); + + // Expected ciphertext and tag from NIST + var expected_ciphertext: [24]u8 = undefined; + _ = try hexToBytes(&expected_ciphertext, "e3b201a9f5b71a7a9b1ceaeccd97e70b6176aad9a4428aa5"); + var expected_tag: [8]u8 = undefined; + _ = try hexToBytes(&expected_tag, "484392fbc1b09951"); + + // Encrypt + var c: [plaintext.len]u8 = undefined; + var tag: [Aes128Ccm8_12.tag_length]u8 = undefined; + + Aes128Ccm8_12.encrypt(&c, &tag, &plaintext, &ad, nonce, key); + + // Verify ciphertext matches NIST expected output + try testing.expectEqualSlices(u8, &expected_ciphertext, &c); + + // Verify tag matches NIST expected output + try testing.expectEqualSlices(u8, &expected_tag, &tag); + + // Decrypt and verify round-trip + var m: [plaintext.len]u8 = undefined; + try Aes128Ccm8_12.decrypt(&m, &c, tag, &ad, nonce, key); + try testing.expectEqualSlices(u8, &plaintext, &m); +} + +test "Aes128Ccm14 - NIST SP 800-38C Example 4" { + const Aes128Ccm14_13 = AesCcm(crypto.core.aes.Aes128, 14, 13); + + // Example 4 (C.4): Klen=128, Tlen=112, Nlen=104, Alen=524288, Plen=256 + // Note: Associated data is 65536 bytes (256-byte pattern repeated 256 times) + var key: [16]u8 = undefined; + _ = try hexToBytes(&key, "404142434445464748494a4b4c4d4e4f"); + var nonce: [13]u8 = undefined; + _ = try hexToBytes(&nonce, "101112131415161718191a1b1c"); + var plaintext: [32]u8 = undefined; + _ = try hexToBytes(&plaintext, "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"); + + // Generate 65536-byte associated data (256-byte pattern repeated 256 times) + var pattern: [256]u8 = undefined; + _ = try hexToBytes(&pattern, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"); + + var ad: [65536]u8 = undefined; + for (0..256) |i| { + @memcpy(ad[i * 256 .. (i + 1) * 256], &pattern); + } + + // Expected ciphertext and tag from NIST + var expected_ciphertext: [32]u8 = undefined; + _ = try hexToBytes(&expected_ciphertext, "69915dad1e84c6376a68c2967e4dab615ae0fd1faec44cc484828529463ccf72"); + var expected_tag: [14]u8 = undefined; + _ = try hexToBytes(&expected_tag, "b4ac6bec93e8598e7f0dadbcea5b"); + + // Encrypt + var c: [plaintext.len]u8 = undefined; + var tag: [Aes128Ccm14_13.tag_length]u8 = undefined; + + Aes128Ccm14_13.encrypt(&c, &tag, &plaintext, &ad, nonce, key); + + // Verify ciphertext matches NIST expected output + try testing.expectEqualSlices(u8, &expected_ciphertext, &c); + + // Verify tag matches NIST expected output + try testing.expectEqualSlices(u8, &expected_tag, &tag); + + // Decrypt and verify round-trip + var m: [plaintext.len]u8 = undefined; + try Aes128Ccm14_13.decrypt(&m, &c, tag, &ad, nonce, key); + try testing.expectEqualSlices(u8, &plaintext, &m); +} + +// CCM* test vectors (encryption-only mode with M=0) + +test "Aes128Ccm0 - IEEE 802.15.4 Data Frame (Encryption-only)" { + // IEEE 802.15.4 test vector from section 2.7 + // Security level 0x04 (ENC, encryption without authentication) + var key: [16]u8 = undefined; + _ = try hexToBytes(&key, "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF"); + var nonce: [13]u8 = undefined; + _ = try hexToBytes(&nonce, "ACDE48000000000100000005" ++ "04"); + var plaintext: [4]u8 = undefined; + _ = try hexToBytes(&plaintext, "61626364"); + var ad: [26]u8 = undefined; + _ = try hexToBytes(&ad, "69DC84214302000000004DEAC010000000048DEAC04050000"); + + // Expected ciphertext from IEEE spec + var expected_ciphertext: [4]u8 = undefined; + _ = try hexToBytes(&expected_ciphertext, "D43E022B"); + + // Encrypt + var c: [plaintext.len]u8 = undefined; + var tag: [Aes128Ccm0.tag_length]u8 = undefined; + + Aes128Ccm0.encrypt(&c, &tag, &plaintext, &ad, nonce, key); + + // Verify ciphertext matches IEEE expected output + try testing.expectEqualSlices(u8, &expected_ciphertext, &c); + + // Decrypt and verify round-trip + var m: [plaintext.len]u8 = undefined; + try Aes128Ccm0.decrypt(&m, &c, tag, &ad, nonce, key); + try testing.expectEqualSlices(u8, &plaintext, &m); +} + +test "Aes128Ccm0 - Zero-length plaintext with encryption-only" { + const key: [16]u8 = [_]u8{0x42} ** 16; + const nonce: [13]u8 = [_]u8{0x11} ** 13; + const m = ""; + const ad = "some associated data"; + var c: [m.len]u8 = undefined; + var m2: [m.len]u8 = undefined; + var tag: [Aes128Ccm0.tag_length]u8 = undefined; + + Aes128Ccm0.encrypt(&c, &tag, m, ad, nonce, key); + + try Aes128Ccm0.decrypt(&m2, &c, tag, ad, nonce, key); + + try testing.expectEqual(@as(usize, 0), m2.len); +} + +test "Aes256Ccm0 - Basic encryption-only round-trip" { + const key: [32]u8 = [_]u8{0x42} ** 32; + const nonce: [13]u8 = [_]u8{0x11} ** 13; + const m = "Hello, CCM* encryption-only mode!"; + var c: [m.len]u8 = undefined; + var m2: [m.len]u8 = undefined; + var tag: [Aes256Ccm0.tag_length]u8 = undefined; + + Aes256Ccm0.encrypt(&c, &tag, m, "", nonce, key); + + try Aes256Ccm0.decrypt(&m2, &c, tag, "", nonce, key); + + try testing.expectEqualSlices(u8, m[0..], m2[0..]); +} diff --git a/lib/std/crypto/cbc_mac.zig b/lib/std/crypto/cbc_mac.zig new file mode 100644 index 0000000000..a62f647f92 --- /dev/null +++ b/lib/std/crypto/cbc_mac.zig @@ -0,0 +1,152 @@ +const std = @import("std"); +const crypto = std.crypto; +const mem = std.mem; + +/// CBC-MAC with AES-128 - FIPS 113 https://csrc.nist.gov/publications/detail/fips/113/archive/1985-05-30 +pub const CbcMacAes128 = CbcMac(crypto.core.aes.Aes128); + +/// FIPS 113 (1985): Computer Data Authentication +/// https://csrc.nist.gov/publications/detail/fips/113/archive/1985-05-30 +/// +/// WARNING: CBC-MAC is insecure for variable-length messages without additional +/// protection. Only use when required by protocols like CCM that mitigate this. +pub fn CbcMac(comptime BlockCipher: type) type { + const BlockCipherCtx = @typeInfo(@TypeOf(BlockCipher.initEnc)).@"fn".return_type.?; + const Block = [BlockCipher.block.block_length]u8; + + return struct { + const Self = @This(); + pub const key_length = BlockCipher.key_bits / 8; + pub const block_length = BlockCipher.block.block_length; + pub const mac_length = block_length; + + cipher_ctx: BlockCipherCtx, + buf: Block = [_]u8{0} ** block_length, + pos: usize = 0, + + pub fn create(out: *[mac_length]u8, msg: []const u8, key: *const [key_length]u8) void { + var ctx = Self.init(key); + ctx.update(msg); + ctx.final(out); + } + + pub fn init(key: *const [key_length]u8) Self { + return Self{ + .cipher_ctx = BlockCipher.initEnc(key.*), + }; + } + + pub fn update(self: *Self, msg: []const u8) void { + const left = block_length - self.pos; + var m = msg; + + // Partial buffer exists from previous update. Complete the block. + if (m.len > left) { + for (self.buf[self.pos..], 0..) |*b, i| b.* ^= m[i]; + m = m[left..]; + self.cipher_ctx.encrypt(&self.buf, &self.buf); + self.pos = 0; + } + + // Full blocks. + while (m.len > block_length) { + for (self.buf[0..block_length], 0..) |*b, i| b.* ^= m[i]; + m = m[block_length..]; + self.cipher_ctx.encrypt(&self.buf, &self.buf); + self.pos = 0; + } + + // Copy any remainder for next pass. + if (m.len > 0) { + for (self.buf[self.pos..][0..m.len], 0..) |*b, i| b.* ^= m[i]; + self.pos += m.len; + } + } + + pub fn final(self: *Self, out: *[mac_length]u8) void { + // CBC-MAC: encrypt the current buffer state. + // Partial blocks are implicitly zero-padded: buf[pos..] contains zeros from initialization. + self.cipher_ctx.encrypt(out, &self.buf); + } + }; +} + +const testing = std.testing; + +test "CbcMacAes128 - Empty message" { + const key = [_]u8{ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c }; + var msg: [0]u8 = undefined; + + // CBC-MAC of empty message = Encrypt(0) + const expected = [_]u8{ 0x7d, 0xf7, 0x6b, 0x0c, 0x1a, 0xb8, 0x99, 0xb3, 0x3e, 0x42, 0xf0, 0x47, 0xb9, 0x1b, 0x54, 0x6f }; + + var out: [CbcMacAes128.mac_length]u8 = undefined; + CbcMacAes128.create(&out, &msg, &key); + try testing.expectEqualSlices(u8, &out, &expected); +} + +test "CbcMacAes128 - Single block (16 bytes)" { + const key = [_]u8{ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c }; + const msg = [_]u8{ 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a }; + + // CBC-MAC = Encrypt(msg XOR 0) + const expected = [_]u8{ 0x3a, 0xd7, 0x7b, 0xb4, 0x0d, 0x7a, 0x36, 0x60, 0xa8, 0x9e, 0xca, 0xf3, 0x24, 0x66, 0xef, 0x97 }; + + var out: [CbcMacAes128.mac_length]u8 = undefined; + CbcMacAes128.create(&out, &msg, &key); + try testing.expectEqualSlices(u8, &out, &expected); +} + +test "CbcMacAes128 - Multiple blocks (40 bytes)" { + const key = [_]u8{ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c }; + const msg = [_]u8{ + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, + 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, + }; + + // CBC-MAC processes: block1 | block2 | block3 (last 8 bytes zero-padded) + const expected = [_]u8{ 0x07, 0xd1, 0x92, 0xe3, 0xe6, 0xf0, 0x99, 0xed, 0xcc, 0x39, 0xfd, 0xe6, 0xd0, 0x9c, 0x76, 0x2d }; + + var out: [CbcMacAes128.mac_length]u8 = undefined; + CbcMacAes128.create(&out, &msg, &key); + try testing.expectEqualSlices(u8, &out, &expected); +} + +test "CbcMacAes128 - Incremental update" { + const key = [_]u8{ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c }; + const msg = [_]u8{ + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, + }; + + // Process in chunks + var ctx = CbcMacAes128.init(&key); + ctx.update(msg[0..10]); + ctx.update(msg[10..20]); + ctx.update(msg[20..]); + + var out1: [CbcMacAes128.mac_length]u8 = undefined; + ctx.final(&out1); + + // Compare with one-shot processing + var out2: [CbcMacAes128.mac_length]u8 = undefined; + CbcMacAes128.create(&out2, &msg, &key); + + try testing.expectEqualSlices(u8, &out1, &out2); +} + +test "CbcMacAes128 - Different from CMAC" { + // Verify that CBC-MAC and CMAC produce different outputs + const key = [_]u8{ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c }; + const msg = [_]u8{ 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a }; + + var cbc_mac_out: [CbcMacAes128.mac_length]u8 = undefined; + CbcMacAes128.create(&cbc_mac_out, &msg, &key); + + // CMAC output for same input (from RFC 4493) + const cmac_out = [_]u8{ 0x07, 0x0a, 0x16, 0xb4, 0x6b, 0x4d, 0x41, 0x44, 0xf7, 0x9b, 0xdd, 0x9d, 0xd0, 0x4a, 0x28, 0x7c }; + + // They should be different + try testing.expect(!mem.eql(u8, &cbc_mac_out, &cmac_out)); +} diff --git a/lib/std/crypto/tls/Client.zig b/lib/std/crypto/tls/Client.zig index 8cfa942399..b697d624fa 100644 --- a/lib/std/crypto/tls/Client.zig +++ b/lib/std/crypto/tls/Client.zig @@ -942,7 +942,6 @@ fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize if (prepared.cleartext_len < buf.len) break :done; } for (data[0 .. data.len - 1]) |buf| { - if (buf.len < min_buffer_len) break :done; const prepared = prepareCiphertextRecord(c, ciphertext_buf[ciphertext_end..], buf, .application_data); total_clear += prepared.cleartext_len; ciphertext_end += prepared.ciphertext_end; @@ -950,7 +949,6 @@ fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize } const buf = data[data.len - 1]; for (0..splat) |_| { - if (buf.len < min_buffer_len) break :done; const prepared = prepareCiphertextRecord(c, ciphertext_buf[ciphertext_end..], buf, .application_data); total_clear += prepared.cleartext_len; ciphertext_end += prepared.ciphertext_end; diff --git a/lib/std/debug.zig b/lib/std/debug.zig index bd849a32d3..7ac6c45903 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1,24 +1,89 @@ -const builtin = @import("builtin"); const std = @import("std.zig"); const math = std.math; const mem = std.mem; const posix = std.posix; const fs = std.fs; const testing = std.testing; -const root = @import("root"); +const Allocator = mem.Allocator; const File = std.fs.File; const windows = std.os.windows; -const native_arch = builtin.cpu.arch; -const native_os = builtin.os.tag; -const native_endian = native_arch.endian(); const Writer = std.Io.Writer; const tty = std.Io.tty; +const builtin = @import("builtin"); +const native_arch = builtin.cpu.arch; +const native_os = builtin.os.tag; + +const root = @import("root"); + pub const Dwarf = @import("debug/Dwarf.zig"); pub const Pdb = @import("debug/Pdb.zig"); -pub const SelfInfo = @import("debug/SelfInfo.zig"); +pub const ElfFile = @import("debug/ElfFile.zig"); pub const Info = @import("debug/Info.zig"); pub const Coverage = @import("debug/Coverage.zig"); +pub const cpu_context = @import("debug/cpu_context.zig"); + +/// This type abstracts the target-specific implementation of accessing this process' own debug +/// information behind a generic interface which supports looking up source locations associated +/// with addresses, as well as unwinding the stack where a safe mechanism to do so exists. +/// +/// The Zig Standard Library provides default implementations of `SelfInfo` for common targets, but +/// the implementation can be overriden by exposing `root.debug.SelfInfo`. Setting `SelfInfo` to +/// `void` indicates that the `SelfInfo` API is not supported. +/// +/// This type must expose the following declarations: +/// +/// ``` +/// pub const init: SelfInfo; +/// pub fn deinit(si: *SelfInfo, gpa: Allocator) void; +/// +/// /// Returns the symbol and source location of the instruction at `address`. +/// pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) SelfInfoError!Symbol; +/// /// Returns a name for the "module" (e.g. shared library or executable image) containing `address`. +/// pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) SelfInfoError![]const u8; +/// +/// /// Whether a reliable stack unwinding strategy, such as DWARF unwinding, is available. +/// pub const can_unwind: bool; +/// /// Only required if `can_unwind == true`. +/// pub const UnwindContext = struct { +/// /// An address representing the instruction pointer in the last frame. +/// pc: usize, +/// +/// pub fn init(ctx: *cpu_context.Native, gpa: Allocator) Allocator.Error!UnwindContext; +/// pub fn deinit(ctx: *UnwindContext, gpa: Allocator) void; +/// /// Returns the frame pointer associated with the last unwound stack frame. +/// /// If the frame pointer is unknown, 0 may be returned instead. +/// pub fn getFp(uc: *UnwindContext) usize; +/// }; +/// /// Only required if `can_unwind == true`. Unwinds a single stack frame, returning the frame's +/// /// return address, or 0 if the end of the stack has been reached. +/// pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) SelfInfoError!usize; +/// ``` +pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfInfo")) + root.debug.SelfInfo +else switch (std.Target.ObjectFormat.default(native_os, native_arch)) { + .coff => if (native_os == .windows) @import("debug/SelfInfo/Windows.zig") else void, + .elf => switch (native_os) { + .freestanding, .other => void, + else => @import("debug/SelfInfo/Elf.zig"), + }, + .macho => @import("debug/SelfInfo/MachO.zig"), + .goff, .plan9, .spirv, .wasm, .xcoff => void, + .c, .hex, .raw => unreachable, +}; + +pub const SelfInfoError = error{ + /// The required debug info is invalid or corrupted. + InvalidDebugInfo, + /// The required debug info could not be found. + MissingDebugInfo, + /// The required debug info was found, and may be valid, but is not supported by this implementation. + UnsupportedDebugInfo, + /// The required debug info could not be read from disk due to some IO error. + ReadFailed, + OutOfMemory, + Unexpected, +}; pub const simple_panic = @import("debug/simple_panic.zig"); pub const no_panic = @import("debug/no_panic.zig"); @@ -153,9 +218,14 @@ pub const SourceLocation = struct { }; pub const Symbol = struct { - name: []const u8 = "???", - compile_unit_name: []const u8 = "???", - source_location: ?SourceLocation = null, + name: ?[]const u8, + compile_unit_name: ?[]const u8, + source_location: ?SourceLocation, + pub const unknown: Symbol = .{ + .name = null, + .compile_unit_name = null, + .source_location = null, + }; }; /// Deprecated because it returns the optimization mode of the standard @@ -166,18 +236,12 @@ pub const runtime_safety = switch (builtin.mode) { .ReleaseFast, .ReleaseSmall => false, }; +/// Whether we can unwind the stack on this target, allowing capturing and/or printing the current +/// stack trace. It is still legal to call `captureCurrentStackTrace`, `writeCurrentStackTrace`, and +/// `dumpCurrentStackTrace` if this is `false`; it will just print an error / capture an empty +/// trace due to missing functionality. This value is just intended as a heuristic to avoid +/// pointless work e.g. capturing always-empty stack traces. pub const sys_can_stack_trace = switch (builtin.cpu.arch) { - // Observed to go into an infinite loop. - // TODO: Make this work. - .loongarch32, - .loongarch64, - .mips, - .mipsel, - .mips64, - .mips64el, - .s390x, - => false, - // `@returnAddress()` in LLVM 10 gives // "Non-Emscripten WebAssembly hasn't implemented __builtin_return_address". // On Emscripten, Zig only supports `@returnAddress()` in debug builds @@ -186,7 +250,7 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) { .wasm64, => native_os == .emscripten and builtin.mode == .Debug, - // `@returnAddress()` is unsupported in LLVM 13. + // `@returnAddress()` is unsupported in LLVM 21. .bpfel, .bpfeb, => false, @@ -209,8 +273,12 @@ pub fn unlockStdErr() void { /// /// During the lock, any `std.Progress` information is cleared from the terminal. /// -/// Returns a `Writer` with empty buffer, meaning that it is -/// in fact unbuffered and does not need to be flushed. +/// The lock is recursive, so it is valid for the same thread to call `lockStderrWriter` multiple +/// times. The primary motivation is that this allows the panic handler to safely dump the stack +/// trace and panic message even if the mutex was held at the panic site. +/// +/// The returned `Writer` does not need to be manually flushed: flushing is performed automatically +/// when the matching `unlockStderrWriter` call occurs. pub fn lockStderrWriter(buffer: []u8) *Writer { return std.Progress.lockStderrWriter(buffer); } @@ -231,16 +299,13 @@ pub fn print(comptime fmt: []const u8, args: anytype) void { nosuspend bw.print(fmt, args) catch return; } -/// TODO multithreaded awareness -var self_debug_info: ?SelfInfo = null; - -pub fn getSelfDebugInfo() !*SelfInfo { - if (self_debug_info) |*info| { - return info; - } else { - self_debug_info = try SelfInfo.open(getDebugInfoAllocator()); - return &self_debug_info.?; - } +/// Marked `inline` to propagate a comptime-known error to callers. +pub inline fn getSelfDebugInfo() !*SelfInfo { + if (SelfInfo == void) return error.UnsupportedTarget; + const S = struct { + var self_info: SelfInfo = .init; + }; + return &S.self_info; } /// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned. @@ -321,226 +386,8 @@ test dumpHexFallible { try std.testing.expectEqualStrings(expected, aw.written()); } -/// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned. -pub fn dumpCurrentStackTrace(start_addr: ?usize) void { - const stderr = lockStderrWriter(&.{}); - defer unlockStderrWriter(); - nosuspend dumpCurrentStackTraceToWriter(start_addr, stderr) catch return; -} - -/// Prints the current stack trace to the provided writer. -pub fn dumpCurrentStackTraceToWriter(start_addr: ?usize, writer: *Writer) !void { - if (builtin.target.cpu.arch.isWasm()) { - if (native_os == .wasi) { - try writer.writeAll("Unable to dump stack trace: not implemented for Wasm\n"); - } - return; - } - if (builtin.strip_debug_info) { - try writer.writeAll("Unable to dump stack trace: debug info stripped\n"); - return; - } - const debug_info = getSelfDebugInfo() catch |err| { - try writer.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}); - return; - }; - writeCurrentStackTrace(writer, debug_info, tty.detectConfig(.stderr()), start_addr) catch |err| { - try writer.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}); - return; - }; -} - -pub const have_ucontext = posix.ucontext_t != void; - -/// Platform-specific thread state. This contains register state, and on some platforms -/// information about the stack. This is not safe to trivially copy, because some platforms -/// use internal pointers within this structure. To make a copy, use `copyContext`. -pub const ThreadContext = blk: { - if (native_os == .windows) { - break :blk windows.CONTEXT; - } else if (have_ucontext) { - break :blk posix.ucontext_t; - } else { - break :blk void; - } -}; - -/// Copies one context to another, updating any internal pointers -pub fn copyContext(source: *const ThreadContext, dest: *ThreadContext) void { - if (!have_ucontext) return {}; - dest.* = source.*; - relocateContext(dest); -} - -/// Updates any internal pointers in the context to reflect its current location -pub fn relocateContext(context: *ThreadContext) void { - return switch (native_os) { - .macos => { - context.mcontext = &context.__mcontext_data; - }, - else => {}, - }; -} - -pub const have_getcontext = @TypeOf(posix.system.getcontext) != void; - -/// Capture the current context. The register values in the context will reflect the -/// state after the platform `getcontext` function returns. -/// -/// It is valid to call this if the platform doesn't have context capturing support, -/// in that case false will be returned. -pub inline fn getContext(context: *ThreadContext) bool { - if (native_os == .windows) { - context.* = std.mem.zeroes(windows.CONTEXT); - windows.ntdll.RtlCaptureContext(context); - return true; - } - - const result = have_getcontext and posix.system.getcontext(context) == 0; - if (native_os == .macos) { - assert(context.mcsize == @sizeOf(std.c.mcontext_t)); - - // On aarch64-macos, the system getcontext doesn't write anything into the pc - // register slot, it only writes lr. This makes the context consistent with - // other aarch64 getcontext implementations which write the current lr - // (where getcontext will return to) into both the lr and pc slot of the context. - if (native_arch == .aarch64) context.mcontext.ss.pc = context.mcontext.ss.lr; - } - - return result; -} - -/// Tries to print the stack trace starting from the supplied base pointer to stderr, -/// unbuffered, and ignores any error returned. -/// TODO multithreaded awareness -pub fn dumpStackTraceFromBase(context: *ThreadContext, stderr: *Writer) void { - nosuspend { - if (builtin.target.cpu.arch.isWasm()) { - if (native_os == .wasi) { - stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return; - } - return; - } - if (builtin.strip_debug_info) { - stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; - return; - } - const debug_info = getSelfDebugInfo() catch |err| { - stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; - return; - }; - const tty_config = tty.detectConfig(.stderr()); - if (native_os == .windows) { - // On x86_64 and aarch64, the stack will be unwound using RtlVirtualUnwind using the context - // provided by the exception handler. On x86, RtlVirtualUnwind doesn't exist. Instead, a new backtrace - // will be captured and frames prior to the exception will be filtered. - // The caveat is that RtlCaptureStackBackTrace does not include the KiUserExceptionDispatcher frame, - // which is where the IP in `context` points to, so it can't be used as start_addr. - // Instead, start_addr is recovered from the stack. - const start_addr = if (builtin.cpu.arch == .x86) @as(*const usize, @ptrFromInt(context.getRegs().bp + 4)).* else null; - writeStackTraceWindows(stderr, debug_info, tty_config, context, start_addr) catch return; - return; - } - - var it = StackIterator.initWithContext(null, debug_info, context, @frameAddress()) catch return; - defer it.deinit(); - - // DWARF unwinding on aarch64-macos is not complete so we need to get pc address from mcontext - const pc_addr = if (builtin.target.os.tag.isDarwin() and native_arch == .aarch64) - context.mcontext.ss.pc - else - it.unwind_state.?.dwarf_context.pc; - printSourceAtAddress(debug_info, stderr, pc_addr, tty_config) catch return; - - while (it.next()) |return_address| { - printLastUnwindError(&it, debug_info, stderr, tty_config); - - // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, - // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid - // an overflow. We do not need to signal `StackIterator` as it will correctly detect this - // condition on the subsequent iteration and return `null` thus terminating the loop. - // same behaviour for x86-windows-msvc - const address = if (return_address == 0) return_address else return_address - 1; - printSourceAtAddress(debug_info, stderr, address, tty_config) catch return; - } else printLastUnwindError(&it, debug_info, stderr, tty_config); - } -} - -/// Returns a slice with the same pointer as addresses, with a potentially smaller len. -/// On Windows, when first_address is not null, we ask for at least 32 stack frames, -/// and then try to find the first address. If addresses.len is more than 32, we -/// capture that many stack frames exactly, and then look for the first address, -/// chopping off the irrelevant frames and shifting so that the returned addresses pointer -/// equals the passed in addresses pointer. -pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackTrace) void { - if (native_os == .windows) { - const addrs = stack_trace.instruction_addresses; - const first_addr = first_address orelse { - stack_trace.index = walkStackWindows(addrs[0..], null); - return; - }; - var addr_buf_stack: [32]usize = undefined; - const addr_buf = if (addr_buf_stack.len > addrs.len) addr_buf_stack[0..] else addrs; - const n = walkStackWindows(addr_buf[0..], null); - const first_index = for (addr_buf[0..n], 0..) |addr, i| { - if (addr == first_addr) { - break i; - } - } else { - stack_trace.index = 0; - return; - }; - const end_index = @min(first_index + addrs.len, n); - const slice = addr_buf[first_index..end_index]; - // We use a for loop here because slice and addrs may alias. - for (slice, 0..) |addr, i| { - addrs[i] = addr; - } - stack_trace.index = slice.len; - } else { - // TODO: This should use the DWARF unwinder if .eh_frame_hdr is available (so that full debug info parsing isn't required). - // A new path for loading SelfInfo needs to be created which will only attempt to parse in-memory sections, because - // stopping to load other debug info (ie. source line info) from disk here is not required for unwinding. - var it = StackIterator.init(first_address, @frameAddress()); - defer it.deinit(); - for (stack_trace.instruction_addresses, 0..) |*addr, i| { - addr.* = it.next() orelse { - stack_trace.index = i; - return; - }; - } - stack_trace.index = stack_trace.instruction_addresses.len; - } -} - -/// Tries to print a stack trace to stderr, unbuffered, and ignores any error returned. -/// TODO multithreaded awareness -pub fn dumpStackTrace(stack_trace: std.builtin.StackTrace) void { - nosuspend { - if (builtin.target.cpu.arch.isWasm()) { - if (native_os == .wasi) { - const stderr = lockStderrWriter(&.{}); - defer unlockStderrWriter(); - stderr.writeAll("Unable to dump stack trace: not implemented for Wasm\n") catch return; - } - return; - } - const stderr = lockStderrWriter(&.{}); - defer unlockStderrWriter(); - if (builtin.strip_debug_info) { - stderr.writeAll("Unable to dump stack trace: debug info stripped\n") catch return; - return; - } - const debug_info = getSelfDebugInfo() catch |err| { - stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; - return; - }; - writeStackTrace(stack_trace, stderr, debug_info, tty.detectConfig(.stderr())) catch |err| { - stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return; - return; - }; - } -} +/// The pointer through which a `cpu_context.Native` is received from callers of stack tracing logic. +pub const CpuContextPtr = if (cpu_context.Native == noreturn) noreturn else *const cpu_context.Native; /// Invokes detectable illegal behavior when `ok` is `false`. /// @@ -579,8 +426,8 @@ pub fn panic(comptime format: []const u8, args: anytype) noreturn { panicExtra(@returnAddress(), format, args); } -/// Equivalent to `@panic` but with a formatted message, and with an explicitly -/// provided return address. +/// Equivalent to `@panic` but with a formatted message and an explicitly provided return address +/// which will be the first address in the stack trace. pub fn panicExtra( ret_addr: ?usize, comptime format: []const u8, @@ -610,6 +457,20 @@ var panicking = std.atomic.Value(u8).init(0); /// This is used to catch and handle panics triggered by the panic handler. threadlocal var panic_stage: usize = 0; +/// For backends that cannot handle the language features depended on by the +/// default panic handler, we will use a simpler implementation. +const use_trap_panic = switch (builtin.zig_backend) { + .stage2_aarch64, + .stage2_arm, + .stage2_powerpc, + .stage2_riscv64, + .stage2_spirv, + .stage2_wasm, + .stage2_x86, + => true, + else => false, +}; + /// Dumps a stack trace to standard error, then aborts. pub fn defaultPanic( msg: []const u8, @@ -617,23 +478,7 @@ pub fn defaultPanic( ) noreturn { @branchHint(.cold); - // For backends that cannot handle the language features depended on by the - // default panic handler, we have a simpler panic handler: - switch (builtin.zig_backend) { - .stage2_aarch64, - .stage2_arm, - .stage2_powerpc, - .stage2_riscv64, - .stage2_spirv, - .stage2_wasm, - .stage2_x86, - => @trap(), - .stage2_x86_64 => switch (builtin.target.ofmt) { - .elf, .macho => {}, - else => @trap(), - }, - else => {}, - } + if (use_trap_panic) @trap(); switch (builtin.os.tag) { .freestanding, .other => { @@ -683,41 +528,48 @@ pub fn defaultPanic( resetSegfaultHandler(); } - // Note there is similar logic in handleSegfaultPosix and handleSegfaultWindowsExtra. - nosuspend switch (panic_stage) { + // There is very similar logic to the following in `handleSegfault`. + switch (panic_stage) { 0 => { panic_stage = 1; - _ = panicking.fetchAdd(1, .seq_cst); - { + trace: { + const tty_config = tty.detectConfig(.stderr()); + const stderr = lockStderrWriter(&.{}); defer unlockStderrWriter(); if (builtin.single_threaded) { - stderr.print("panic: ", .{}) catch posix.abort(); + stderr.print("panic: ", .{}) catch break :trace; } else { const current_thread_id = std.Thread.getCurrentId(); - stderr.print("thread {} panic: ", .{current_thread_id}) catch posix.abort(); + stderr.print("thread {} panic: ", .{current_thread_id}) catch break :trace; } - stderr.print("{s}\n", .{msg}) catch posix.abort(); + stderr.print("{s}\n", .{msg}) catch break :trace; - if (@errorReturnTrace()) |t| dumpStackTrace(t.*); - dumpCurrentStackTraceToWriter(first_trace_addr orelse @returnAddress(), stderr) catch {}; + if (@errorReturnTrace()) |t| if (t.index > 0) { + stderr.writeAll("error return context:\n") catch break :trace; + writeStackTrace(t, stderr, tty_config) catch break :trace; + stderr.writeAll("\nstack trace:\n") catch break :trace; + }; + writeCurrentStackTrace(.{ + .first_address = first_trace_addr orelse @returnAddress(), + .allow_unsafe_unwind = true, // we're crashing anyway, give it our all! + }, stderr, tty_config) catch break :trace; } waitForOtherThreadToFinishPanicking(); }, 1 => { panic_stage = 2; - // A panic happened while trying to print a previous panic message. // We're still holding the mutex but that's fine as we're going to // call abort(). fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {}; }, else => {}, // Panicked while printing the recursive panic message. - }; + } posix.abort(); } @@ -736,391 +588,464 @@ fn waitForOtherThreadToFinishPanicking() void { } } -pub fn writeStackTrace( - stack_trace: std.builtin.StackTrace, - writer: *Writer, - debug_info: *SelfInfo, - tty_config: tty.Config, -) !void { - if (builtin.strip_debug_info) return error.MissingDebugInfo; - var frame_index: usize = 0; - var frames_left: usize = @min(stack_trace.index, stack_trace.instruction_addresses.len); - - while (frames_left != 0) : ({ - frames_left -= 1; - frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len; - }) { - const return_address = stack_trace.instruction_addresses[frame_index]; - try printSourceAtAddress(debug_info, writer, return_address - 1, tty_config); - } +pub const StackUnwindOptions = struct { + /// If not `null`, we will ignore all frames up until this return address. This is typically + /// used to omit intermediate handling code (for instance, a panic handler and its machinery) + /// from stack traces. + first_address: ?usize = null, + /// If not `null`, we will unwind from this `cpu_context.Native` instead of the current top of + /// the stack. The main use case here is printing stack traces from signal handlers, where the + /// kernel provides a `*const cpu_context.Native` of the state before the signal. + context: ?CpuContextPtr = null, + /// If `true`, stack unwinding strategies which may cause crashes are used as a last resort. + /// If `false`, only known-safe mechanisms will be attempted. + allow_unsafe_unwind: bool = false, +}; - if (stack_trace.index > stack_trace.instruction_addresses.len) { - const dropped_frames = stack_trace.index - stack_trace.instruction_addresses.len; +/// Capture and return the current stack trace. The returned `StackTrace` stores its addresses in +/// the given buffer, so `addr_buf` must have a lifetime at least equal to the `StackTrace`. +/// +/// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it. +pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) std.builtin.StackTrace { + const empty_trace: std.builtin.StackTrace = .{ .index = 0, .instruction_addresses = &.{} }; + if (!std.options.allow_stack_tracing) return empty_trace; + var it = StackIterator.init(options.context) catch return empty_trace; + defer it.deinit(); + if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace; + var total_frames: usize = 0; + var index: usize = 0; + var wait_for = options.first_address; + // Ideally, we would iterate the whole stack so that the `index` in the returned trace was + // indicative of how many frames were skipped. However, this has a significant runtime cost + // in some cases, so at least for now, we don't do that. + while (index < addr_buf.len) switch (it.next()) { + .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break, + .end => break, + .frame => |ret_addr| { + if (total_frames > 10_000) { + // Limit the number of frames in case of (e.g.) broken debug information which is + // getting unwinding stuck in a loop. + break; + } + total_frames += 1; + if (wait_for) |target| { + if (ret_addr != target) continue; + wait_for = null; + } + addr_buf[index] = ret_addr; + index += 1; + }, + }; + return .{ + .index = index, + .instruction_addresses = addr_buf[0..index], + }; +} +/// Write the current stack trace to `writer`, annotated with source locations. +/// +/// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing. +pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_config: tty.Config) Writer.Error!void { + if (!std.options.allow_stack_tracing) { + tty_config.setColor(writer, .dim) catch {}; + try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{}); + tty_config.setColor(writer, .reset) catch {}; + return; + } + const di_gpa = getDebugInfoAllocator(); + const di = getSelfDebugInfo() catch |err| switch (err) { + error.UnsupportedTarget => { + tty_config.setColor(writer, .dim) catch {}; + try writer.print("Cannot print stack trace: debug info unavailable for target\n", .{}); + tty_config.setColor(writer, .reset) catch {}; + return; + }, + }; + var it = StackIterator.init(options.context) catch |err| switch (err) { + error.CannotUnwindFromContext => { + tty_config.setColor(writer, .dim) catch {}; + try writer.print("Cannot print stack trace: context unwind unavailable for target\n", .{}); + tty_config.setColor(writer, .reset) catch {}; + return; + }, + }; + defer it.deinit(); + if (!it.stratOk(options.allow_unsafe_unwind)) { + tty_config.setColor(writer, .dim) catch {}; + try writer.print("Cannot print stack trace: safe unwind unavailable for target\n", .{}); + tty_config.setColor(writer, .reset) catch {}; + return; + } + var total_frames: usize = 0; + var wait_for = options.first_address; + var printed_any_frame = false; + while (true) switch (it.next()) { + .switch_to_fp => |unwind_error| { + switch (StackIterator.fp_usability) { + .useless, .unsafe => {}, + .safe, .ideal => continue, // no need to even warn + } + const module_name = di.getModuleName(di_gpa, unwind_error.address) catch "???"; + const caption: []const u8 = switch (unwind_error.err) { + error.MissingDebugInfo => "unwind info unavailable", + error.InvalidDebugInfo => "unwind info invalid", + error.UnsupportedDebugInfo => "unwind info unsupported", + error.ReadFailed => "filesystem error", + error.OutOfMemory => "out of memory", + error.Unexpected => "unexpected error", + }; + if (it.stratOk(options.allow_unsafe_unwind)) { + tty_config.setColor(writer, .dim) catch {}; + try writer.print( + "Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n", + .{ module_name, unwind_error.address, caption }, + ); + tty_config.setColor(writer, .reset) catch {}; + } else { + tty_config.setColor(writer, .dim) catch {}; + try writer.print( + "Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n", + .{ module_name, unwind_error.address, caption }, + ); + tty_config.setColor(writer, .reset) catch {}; + return; + } + }, + .end => break, + .frame => |ret_addr| { + if (total_frames > 10_000) { + tty_config.setColor(writer, .dim) catch {}; + try writer.print( + "Stopping trace after {d} frames (large frame count may indicate broken debug info)\n", + .{total_frames}, + ); + tty_config.setColor(writer, .reset) catch {}; + return; + } + total_frames += 1; + if (wait_for) |target| { + if (ret_addr != target) continue; + wait_for = null; + } + // `ret_addr` is the return address, which is *after* the function call. + // Subtract 1 to get an address *in* the function call for a better source location. + try printSourceAtAddress(di_gpa, di, writer, ret_addr -| 1, tty_config); + printed_any_frame = true; + }, + }; + if (!printed_any_frame) return writer.writeAll("(empty stack trace)\n"); +} +/// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors. +pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void { + const tty_config = tty.detectConfig(.stderr()); + const stderr = lockStderrWriter(&.{}); + defer unlockStderrWriter(); + writeCurrentStackTrace(.{ + .first_address = a: { + if (options.first_address) |a| break :a a; + if (options.context != null) break :a null; + break :a @returnAddress(); // don't include this frame in the trace + }, + .context = options.context, + .allow_unsafe_unwind = options.allow_unsafe_unwind, + }, stderr, tty_config) catch |err| switch (err) { + error.WriteFailed => {}, + }; +} +/// Write a previously captured stack trace to `writer`, annotated with source locations. +pub fn writeStackTrace(st: *const std.builtin.StackTrace, writer: *Writer, tty_config: tty.Config) Writer.Error!void { + if (!std.options.allow_stack_tracing) { + tty_config.setColor(writer, .dim) catch {}; + try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{}); + tty_config.setColor(writer, .reset) catch {}; + return; + } + // Fetch `st.index` straight away. Aside from avoiding redundant loads, this prevents issues if + // `st` is `@errorReturnTrace()` and errors are encountered while writing the stack trace. + const n_frames = st.index; + if (n_frames == 0) return writer.writeAll("(empty stack trace)\n"); + const di_gpa = getDebugInfoAllocator(); + const di = getSelfDebugInfo() catch |err| switch (err) { + error.UnsupportedTarget => { + tty_config.setColor(writer, .dim) catch {}; + try writer.print("Cannot print stack trace: debug info unavailable for target\n\n", .{}); + tty_config.setColor(writer, .reset) catch {}; + return; + }, + }; + const captured_frames = @min(n_frames, st.instruction_addresses.len); + for (st.instruction_addresses[0..captured_frames]) |ret_addr| { + // `ret_addr` is the return address, which is *after* the function call. + // Subtract 1 to get an address *in* the function call for a better source location. + try printSourceAtAddress(di_gpa, di, writer, ret_addr -| 1, tty_config); + } + if (n_frames > captured_frames) { tty_config.setColor(writer, .bold) catch {}; - try writer.print("({d} additional stack frames skipped...)\n", .{dropped_frames}); + try writer.print("({d} additional stack frames skipped...)\n", .{n_frames - captured_frames}); tty_config.setColor(writer, .reset) catch {}; } } +/// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors. +pub fn dumpStackTrace(st: *const std.builtin.StackTrace) void { + const tty_config = tty.detectConfig(.stderr()); + const stderr = lockStderrWriter(&.{}); + defer unlockStderrWriter(); + writeStackTrace(st, stderr, tty_config) catch |err| switch (err) { + error.WriteFailed => {}, + }; +} -pub const UnwindError = if (have_ucontext) - @typeInfo(@typeInfo(@TypeOf(StackIterator.next_unwind)).@"fn".return_type.?).error_union.error_set -else - void; - -pub const StackIterator = struct { - // Skip every frame before this address is found. - first_address: ?usize, - // Last known value of the frame pointer register. +const StackIterator = union(enum) { + /// Unwinding using debug info (e.g. DWARF CFI). + di: if (SelfInfo != void and SelfInfo.can_unwind) SelfInfo.UnwindContext else noreturn, + /// We will first report the *current* PC of this `UnwindContext`, then we will switch to `di`. + di_first: if (SelfInfo != void and SelfInfo.can_unwind) SelfInfo.UnwindContext else noreturn, + /// Naive frame-pointer-based unwinding. Very simple, but typically unreliable. fp: usize, - // When SelfInfo and a register context is available, this iterator can unwind - // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer), - // using DWARF and MachO unwind info. - unwind_state: if (have_ucontext) ?struct { - debug_info: *SelfInfo, - dwarf_context: SelfInfo.UnwindContext, - last_error: ?UnwindError = null, - failed: bool = false, - } else void = if (have_ucontext) null else {}, - - pub fn init(first_address: ?usize, fp: usize) StackIterator { - if (native_arch.isSPARC()) { + /// It is important that this function is marked `inline` so that it can safely use + /// `@frameAddress` and `cpu_context.Native.current` as the caller's stack frame and + /// our own are one and the same. + inline fn init(opt_context_ptr: ?CpuContextPtr) error{CannotUnwindFromContext}!StackIterator { + if (builtin.cpu.arch.isSPARC()) { // Flush all the register windows on stack. - asm volatile (if (builtin.cpu.has(.sparc, .v9)) - "flushw" - else - "ta 3" // ST_FLUSH_WINDOWS - ::: .{ .memory = true }); - } - - return .{ - .first_address = first_address, - .fp = fp, - }; - } - - pub fn initWithContext(first_address: ?usize, debug_info: *SelfInfo, context: *posix.ucontext_t, fp: usize) !StackIterator { - // The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that - // the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder. - if (builtin.target.os.tag.isDarwin() and native_arch == .aarch64) - return init(first_address, @truncate(context.mcontext.ss.fp)); - - if (SelfInfo.supports_unwinding) { - var iterator = init(first_address, fp); - iterator.unwind_state = .{ - .debug_info = debug_info, - .dwarf_context = try SelfInfo.UnwindContext.init(debug_info.allocator, context), - }; - return iterator; - } - - return init(first_address, fp); - } - - pub fn deinit(it: *StackIterator) void { - if (have_ucontext and it.unwind_state != null) it.unwind_state.?.dwarf_context.deinit(); - } - - pub fn getLastError(it: *StackIterator) ?struct { - err: UnwindError, - address: usize, - } { - if (!have_ucontext) return null; - if (it.unwind_state) |*unwind_state| { - if (unwind_state.last_error) |err| { - unwind_state.last_error = null; - return .{ - .err = err, - .address = unwind_state.dwarf_context.pc, - }; + if (builtin.cpu.has(.sparc, .v9)) { + asm volatile ("flushw" ::: .{ .memory = true }); + } else { + asm volatile ("ta 3" ::: .{ .memory = true }); // ST_FLUSH_WINDOWS } } - - return null; - } - - // Offset of the saved BP wrt the frame pointer. - const fp_offset = if (native_arch.isRISCV()) - // On RISC-V the frame pointer points to the top of the saved register - // area, on pretty much every other architecture it points to the stack - // slot where the previous frame pointer is saved. - 2 * @sizeOf(usize) - else if (native_arch.isSPARC()) - // On SPARC the previous frame pointer is stored at 14 slots past %fp+BIAS. - 14 * @sizeOf(usize) - else - 0; - - const fp_bias = if (native_arch.isSPARC()) - // On SPARC frame pointers are biased by a constant. - 2047 - else - 0; - - // Positive offset of the saved PC wrt the frame pointer. - const pc_offset = if (native_arch == .powerpc64le) - 2 * @sizeOf(usize) - else - @sizeOf(usize); - - pub fn next(it: *StackIterator) ?usize { - var address = it.next_internal() orelse return null; - - if (it.first_address) |first_address| { - while (address != first_address) { - address = it.next_internal() orelse return null; - } - it.first_address = null; + if (opt_context_ptr) |context_ptr| { + if (SelfInfo == void or !SelfInfo.can_unwind) return error.CannotUnwindFromContext; + // Use `di_first` here so we report the PC in the context before unwinding any further. + return .{ .di_first = .init(context_ptr) }; } - - return address; - } - - fn next_unwind(it: *StackIterator) !usize { - const unwind_state = &it.unwind_state.?; - const module = try unwind_state.debug_info.getModuleForAddress(unwind_state.dwarf_context.pc); - switch (native_os) { - .macos, .ios, .watchos, .tvos, .visionos => { - // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding - // via DWARF before attempting to use the compact unwind info will produce incorrect results. - if (module.unwind_info) |unwind_info| { - if (SelfInfo.unwindFrameMachO( - unwind_state.debug_info.allocator, - module.base_address, - &unwind_state.dwarf_context, - unwind_info, - module.eh_frame, - )) |return_address| { - return return_address; - } else |err| { - if (err != error.RequiresDWARFUnwind) return err; - } - } else return error.MissingUnwindInfo; - }, - else => {}, + // Workaround the C backend being unable to use inline assembly on MSVC by disabling the + // call to `current`. This effectively constrains stack trace collection and dumping to FP + // unwinding when building with CBE for MSVC. + if (!(builtin.zig_backend == .stage2_c and builtin.target.abi == .msvc) and + SelfInfo != void and + SelfInfo.can_unwind and + cpu_context.Native != noreturn and + fp_usability != .ideal) + { + // We don't need `di_first` here, because our PC is in `std.debug`; we're only interested + // in our caller's frame and above. + return .{ .di = .init(&.current()) }; } - - if (try module.getDwarfInfoForAddress(unwind_state.debug_info.allocator, unwind_state.dwarf_context.pc)) |di| { - return SelfInfo.unwindFrameDwarf( - unwind_state.debug_info.allocator, - di, - module.base_address, - &unwind_state.dwarf_context, - null, - ); - } else return error.MissingDebugInfo; + return .{ .fp = @frameAddress() }; } - - fn next_internal(it: *StackIterator) ?usize { - if (have_ucontext) { - if (it.unwind_state) |*unwind_state| { - if (!unwind_state.failed) { - if (unwind_state.dwarf_context.pc == 0) return null; - defer it.fp = unwind_state.dwarf_context.getFp() catch 0; - if (it.next_unwind()) |return_address| { - return return_address; - } else |err| { - unwind_state.last_error = err; - unwind_state.failed = true; - - // Fall back to fp-based unwinding on the first failure. - // We can't attempt it again for other modules higher in the - // stack because the full register state won't have been unwound. - } - } - } + fn deinit(si: *StackIterator) void { + switch (si.*) { + .fp => {}, + .di, .di_first => |*unwind_context| unwind_context.deinit(getDebugInfoAllocator()), } + } - if (builtin.omit_frame_pointer) return null; - - const fp = if (comptime native_arch.isSPARC()) - // On SPARC the offset is positive. (!) - math.add(usize, it.fp, fp_offset) catch return null - else - math.sub(usize, it.fp, fp_offset) catch return null; - - // Sanity check. - if (fp == 0 or !mem.isAligned(fp, @alignOf(usize))) return null; - const new_fp = math.add(usize, @as(*usize, @ptrFromInt(fp)).*, fp_bias) catch - return null; - - // Sanity check: the stack grows down thus all the parent frames must be - // be at addresses that are greater (or equal) than the previous one. - // A zero frame pointer often signals this is the last frame, that case - // is gracefully handled by the next call to next_internal. - if (new_fp != 0 and new_fp < it.fp) return null; - const new_pc = @as(*usize, @ptrFromInt(math.add(usize, fp, pc_offset) catch return null)).*; - - it.fp = new_fp; + const FpUsability = enum { + /// FP unwinding is impractical on this target. For example, due to its very silly ABI + /// design decisions, it's not possible to do generic FP unwinding on MIPS without a + /// complicated code scanning algorithm. + useless, + /// FP unwinding is unsafe on this target; we may crash when doing so. We will only perform + /// FP unwinding in the case of crashes/panics, or if the user opts in. + unsafe, + /// FP unwinding is guaranteed to be safe on this target. We will do so if unwinding with + /// debug info does not work, and if this compilation has frame pointers enabled. + safe, + /// FP unwinding is the best option on this target. This is usually because the ABI requires + /// a backchain pointer, thus making it always available, safe, and fast. + ideal, + }; - return new_pc; - } -}; + const fp_usability: FpUsability = switch (builtin.target.cpu.arch) { + .mips, + .mipsel, + .mips64, + .mips64el, + => .useless, + .hexagon, + // The PowerPC ABIs don't actually strictly require a backchain pointer; they allow omitting + // it when full unwind info is present. Despite this, both GCC and Clang always enforce the + // presence of the backchain pointer no matter what options they are given. This seems to be + // a case of "the spec is only a polite suggestion", except it works in our favor this time! + .powerpc, + .powerpcle, + .powerpc64, + .powerpc64le, + => .ideal, + // https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms#Respect-the-purpose-of-specific-CPU-registers + .aarch64 => if (builtin.target.os.tag.isDarwin()) .safe else .unsafe, + else => .unsafe, + }; -pub fn writeCurrentStackTrace( - writer: *Writer, - debug_info: *SelfInfo, - tty_config: tty.Config, - start_addr: ?usize, -) !void { - if (native_os == .windows) { - var context: ThreadContext = undefined; - assert(getContext(&context)); - return writeStackTraceWindows(writer, debug_info, tty_config, &context, start_addr); + /// Whether the current unwind strategy is allowed given `allow_unsafe`. + fn stratOk(it: *const StackIterator, allow_unsafe: bool) bool { + return switch (it.*) { + .di, .di_first => true, + // If we omitted frame pointers from *this* compilation, FP unwinding would crash + // immediately regardless of anything. But FPs could also be omitted from a different + // linked object, so it's not guaranteed to be safe, unless the target specifically + // requires it. + .fp => switch (fp_usability) { + .useless => false, + .unsafe => allow_unsafe and !builtin.omit_frame_pointer, + .safe => !builtin.omit_frame_pointer, + .ideal => true, + }, + }; } - var context: ThreadContext = undefined; - const has_context = getContext(&context); - - var it = (if (has_context) blk: { - break :blk StackIterator.initWithContext(start_addr, debug_info, &context, @frameAddress()) catch null; - } else null) orelse StackIterator.init(start_addr, @frameAddress()); - defer it.deinit(); - while (it.next()) |return_address| { - printLastUnwindError(&it, debug_info, writer, tty_config); - - // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, - // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid - // an overflow. We do not need to signal `StackIterator` as it will correctly detect this - // condition on the subsequent iteration and return `null` thus terminating the loop. - // same behaviour for x86-windows-msvc - const address = return_address -| 1; - try printSourceAtAddress(debug_info, writer, address, tty_config); - } else printLastUnwindError(&it, debug_info, writer, tty_config); -} + const Result = union(enum) { + /// A stack frame has been found; this is the corresponding return address. + frame: usize, + /// The end of the stack has been reached. + end, + /// We were using `SelfInfo.UnwindInfo`, but are now switching to FP unwinding due to this error. + switch_to_fp: struct { + address: usize, + err: SelfInfoError, + }, + }; -pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const windows.CONTEXT) usize { - if (builtin.cpu.arch == .x86) { - // RtlVirtualUnwind doesn't exist on x86 - return windows.ntdll.RtlCaptureStackBackTrace(0, addresses.len, @as(**anyopaque, @ptrCast(addresses.ptr)), null); - } + fn next(it: *StackIterator) Result { + switch (it.*) { + .di_first => |unwind_context| { + const first_pc = unwind_context.pc; + if (first_pc == 0) return .end; + it.* = .{ .di = unwind_context }; + // The caller expects *return* addresses, where they will subtract 1 to find the address of the call. + // However, we have the actual current PC, which should not be adjusted. Compensate by adding 1. + return .{ .frame = first_pc +| 1 }; + }, + .di => |*unwind_context| { + const di = getSelfDebugInfo() catch unreachable; + const di_gpa = getDebugInfoAllocator(); + const ret_addr = di.unwindFrame(di_gpa, unwind_context) catch |err| { + const pc = unwind_context.pc; + it.* = .{ .fp = unwind_context.getFp() }; + return .{ .switch_to_fp = .{ + .address = pc, + .err = err, + } }; + }; + if (ret_addr <= 1) return .end; + return .{ .frame = ret_addr }; + }, + .fp => |fp| { + if (fp == 0) return .end; // we reached the "sentinel" base pointer - const tib = &windows.teb().NtTib; + const bp_addr = applyOffset(fp, bp_offset) orelse return .end; + const ra_addr = applyOffset(fp, ra_offset) orelse return .end; - var context: windows.CONTEXT = undefined; - if (existing_context) |context_ptr| { - context = context_ptr.*; - } else { - context = std.mem.zeroes(windows.CONTEXT); - windows.ntdll.RtlCaptureContext(&context); - } + if (bp_addr == 0 or !mem.isAligned(bp_addr, @alignOf(usize)) or + ra_addr == 0 or !mem.isAligned(ra_addr, @alignOf(usize))) + { + // This isn't valid, but it most likely indicates end of stack. + return .end; + } - var i: usize = 0; - var image_base: windows.DWORD64 = undefined; - var history_table: windows.UNWIND_HISTORY_TABLE = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE); - - while (i < addresses.len) : (i += 1) { - const current_regs = context.getRegs(); - if (windows.ntdll.RtlLookupFunctionEntry(current_regs.ip, &image_base, &history_table)) |runtime_function| { - var handler_data: ?*anyopaque = null; - var establisher_frame: u64 = undefined; - _ = windows.ntdll.RtlVirtualUnwind( - windows.UNW_FLAG_NHANDLER, - image_base, - current_regs.ip, - runtime_function, - &context, - &handler_data, - &establisher_frame, - null, - ); - } else { - // leaf function - context.setIp(@as(*usize, @ptrFromInt(current_regs.sp)).*); - context.setSp(current_regs.sp + @sizeOf(usize)); - } + const bp_ptr: *const usize = @ptrFromInt(bp_addr); + const ra_ptr: *const usize = @ptrFromInt(ra_addr); + const bp = applyOffset(bp_ptr.*, bp_bias) orelse return .end; - const next_regs = context.getRegs(); - if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) { - break; - } + // The stack grows downards, so `bp > fp` should always hold. If it doesn't, this + // frame is invalid, so we'll treat it as though it we reached end of stack. The + // exception is address 0, which is a graceful end-of-stack signal, in which case + // *this* return address is valid and the *next* iteration will be the last. + if (bp != 0 and bp <= fp) return .end; - if (next_regs.ip == 0) { - break; + it.fp = bp; + const ra = stripInstructionPtrAuthCode(ra_ptr.*); + if (ra <= 1) return .end; + return .{ .frame = ra }; + }, } - - addresses[i] = next_regs.ip; } - return i; -} + /// Offset of the saved base pointer (previous frame pointer) wrt the frame pointer. + const bp_offset = off: { + // On RISC-V the frame pointer points to the top of the saved register + // area, on pretty much every other architecture it points to the stack + // slot where the previous frame pointer is saved. + if (native_arch.isLoongArch() or native_arch.isRISCV()) break :off -2 * @sizeOf(usize); + // On SPARC the previous frame pointer is stored at 14 slots past %fp+BIAS. + if (native_arch.isSPARC()) break :off 14 * @sizeOf(usize); + break :off 0; + }; -pub fn writeStackTraceWindows( - writer: *Writer, - debug_info: *SelfInfo, - tty_config: tty.Config, - context: *const windows.CONTEXT, - start_addr: ?usize, -) !void { - var addr_buf: [1024]usize = undefined; - const n = walkStackWindows(addr_buf[0..], context); - const addrs = addr_buf[0..n]; - const start_i: usize = if (start_addr) |saddr| blk: { - for (addrs, 0..) |addr, i| { - if (addr == saddr) break :blk i; - } - return; - } else 0; - for (addrs[start_i..]) |addr| { - try printSourceAtAddress(debug_info, writer, addr - 1, tty_config); - } -} + /// Offset of the saved return address wrt the frame pointer. + const ra_offset = off: { + if (native_arch.isLoongArch() or native_arch.isRISCV()) break :off -1 * @sizeOf(usize); + if (native_arch.isSPARC()) break :off 15 * @sizeOf(usize); + if (native_arch.isPowerPC64()) break :off 2 * @sizeOf(usize); + // On s390x, r14 is the link register and we need to grab it from its customary slot in the + // register save area (ELF ABI s390x Supplement §1.2.2.2). + if (native_arch == .s390x) break :off 14 * @sizeOf(usize); + break :off @sizeOf(usize); + }; -fn printUnknownSource(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void { - const module_name = debug_info.getModuleNameForAddress(address); - return printLineInfo( - writer, - null, - address, - "???", - module_name orelse "???", - tty_config, - printLineFromFileAnyOs, - ); -} + /// Value to add to a base pointer after loading it from the stack. Yes, SPARC really does this. + const bp_bias = bias: { + if (native_arch.isSPARC()) break :bias 2047; + break :bias 0; + }; -fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writer, tty_config: tty.Config) void { - if (!have_ucontext) return; - if (it.getLastError()) |unwind_error| { - printUnwindError(debug_info, writer, unwind_error.address, unwind_error.err, tty_config) catch {}; + fn applyOffset(addr: usize, comptime off: comptime_int) ?usize { + if (off >= 0) return math.add(usize, addr, off) catch return null; + return math.sub(usize, addr, -off) catch return null; } -} +}; -fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, err: UnwindError, tty_config: tty.Config) !void { - const module_name = debug_info.getModuleNameForAddress(address) orelse "???"; - try tty_config.setColor(writer, .dim); - if (err == error.MissingDebugInfo) { - try writer.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address }); - } else { - try writer.print("Unwind error at address `{s}:0x{x}` ({}), trace may be incomplete\n\n", .{ module_name, address, err }); +/// Some platforms use pointer authentication: the upper bits of instruction pointers contain a +/// signature. This function clears those signature bits to make the pointer directly usable. +pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize { + if (native_arch.isAARCH64()) { + // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it) + // The save / restore is because `xpaclri` operates on x30 (LR) + return asm ( + \\mov x16, x30 + \\mov x30, x15 + \\hint 0x07 + \\mov x15, x30 + \\mov x30, x16 + : [ret] "={x15}" (-> usize), + : [ptr] "{x15}" (ptr), + : .{ .x16 = true }); } - try tty_config.setColor(writer, .reset); -} -pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void { - const module = debug_info.getModuleForAddress(address) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config), - else => return err, - }; + return ptr; +} - const symbol_info = module.getSymbolAtAddress(debug_info.allocator, address) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config), - else => return err, +fn printSourceAtAddress(gpa: Allocator, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void { + const symbol: Symbol = debug_info.getSymbol(gpa, address) catch |err| switch (err) { + error.MissingDebugInfo, + error.UnsupportedDebugInfo, + error.InvalidDebugInfo, + => .unknown, + error.ReadFailed, error.Unexpected => s: { + tty_config.setColor(writer, .dim) catch {}; + try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{}); + tty_config.setColor(writer, .reset) catch {}; + break :s .unknown; + }, + error.OutOfMemory => s: { + tty_config.setColor(writer, .dim) catch {}; + try writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{}); + tty_config.setColor(writer, .reset) catch {}; + break :s .unknown; + }, }; - defer if (symbol_info.source_location) |sl| debug_info.allocator.free(sl.file_name); - + defer if (symbol.source_location) |sl| gpa.free(sl.file_name); return printLineInfo( writer, - symbol_info.source_location, + symbol.source_location, address, - symbol_info.name, - symbol_info.compile_unit_name, + symbol.name orelse "???", + symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???", tty_config, - printLineFromFileAnyOs, ); } - fn printLineInfo( writer: *Writer, source_location: ?SourceLocation, @@ -1128,10 +1053,9 @@ fn printLineInfo( symbol_name: []const u8, compile_unit_name: []const u8, tty_config: tty.Config, - comptime printLineFromFile: anytype, -) !void { +) Writer.Error!void { nosuspend { - try tty_config.setColor(writer, .bold); + tty_config.setColor(writer, .bold) catch {}; if (source_location) |*sl| { try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column }); @@ -1139,11 +1063,11 @@ fn printLineInfo( try writer.writeAll("???:?:?"); } - try tty_config.setColor(writer, .reset); + tty_config.setColor(writer, .reset) catch {}; try writer.writeAll(": "); - try tty_config.setColor(writer, .dim); + tty_config.setColor(writer, .dim) catch {}; try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name }); - try tty_config.setColor(writer, .reset); + tty_config.setColor(writer, .reset) catch {}; try writer.writeAll("\n"); // Show the matching source code line if possible @@ -1154,22 +1078,24 @@ fn printLineInfo( const space_needed = @as(usize, @intCast(sl.column - 1)); try writer.splatByteAll(' ', space_needed); - try tty_config.setColor(writer, .green); + tty_config.setColor(writer, .green) catch {}; try writer.writeAll("^"); - try tty_config.setColor(writer, .reset); + tty_config.setColor(writer, .reset) catch {}; } try writer.writeAll("\n"); - } else |err| switch (err) { - error.EndOfFile, error.FileNotFound => {}, - error.BadPathName => {}, - error.AccessDenied => {}, - else => return err, + } else |_| { + // Ignore all errors; it's a better UX to just print the source location without the + // corresponding line number. The user can always open the source file themselves. } } } } +fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void { + // Allow overriding the target-agnostic source line printing logic by exposing `root.debug.printLineFromFile`. + if (@hasDecl(root, "debug") and @hasDecl(root.debug, "printLineFromFile")) { + return root.debug.printLineFromFile(writer, source_location); + } -fn printLineFromFileAnyOs(writer: *Writer, source_location: SourceLocation) !void { // Need this to always block even in async I/O mode, because this could potentially // be called from e.g. the event loop code crashing. var f = try fs.cwd().openFile(source_location.file_name, .{}); @@ -1223,7 +1149,7 @@ fn printLineFromFileAnyOs(writer: *Writer, source_location: SourceLocation) !voi } } -test printLineFromFileAnyOs { +test printLineFromFile { var aw: Writer.Allocating = .init(std.testing.allocator); defer aw.deinit(); const output_stream = &aw.writer; @@ -1245,9 +1171,9 @@ test printLineFromFileAnyOs { defer allocator.free(path); try test_dir.dir.writeFile(.{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" }); - try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 })); + try expectError(error.EndOfFile, printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 })); - try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); + try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written()); aw.clearRetainingCapacity(); } @@ -1263,11 +1189,11 @@ test printLineFromFileAnyOs { , }); - try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); + try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); try expectEqualStrings("1\n", aw.written()); aw.clearRetainingCapacity(); - try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 3, .column = 0 }); + try printLineFromFile(output_stream, .{ .file_name = path, .line = 3, .column = 0 }); try expectEqualStrings("3\n", aw.written()); aw.clearRetainingCapacity(); } @@ -1286,7 +1212,7 @@ test printLineFromFileAnyOs { try writer.splatByteAll('a', overlap); try writer.flush(); - try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }); + try printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 }); try expectEqualStrings(("a" ** overlap) ++ "\n", aw.written()); aw.clearRetainingCapacity(); } @@ -1300,7 +1226,7 @@ test printLineFromFileAnyOs { const writer = &file_writer.interface; try writer.splatByteAll('a', std.heap.page_size_max); - try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); + try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.written()); aw.clearRetainingCapacity(); } @@ -1314,19 +1240,19 @@ test printLineFromFileAnyOs { const writer = &file_writer.interface; try writer.splatByteAll('a', 3 * std.heap.page_size_max); - try expectError(error.EndOfFile, printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 })); + try expectError(error.EndOfFile, printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 })); - try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); + try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.written()); aw.clearRetainingCapacity(); try writer.writeAll("a\na"); - try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); + try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 }); try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.written()); aw.clearRetainingCapacity(); - try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = 2, .column = 0 }); + try printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 }); try expectEqualStrings("a\n", aw.written()); aw.clearRetainingCapacity(); } @@ -1342,39 +1268,55 @@ test printLineFromFileAnyOs { try writer.splatByteAll('\n', real_file_start); try writer.writeAll("abc\ndef"); - try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 }); + try printLineFromFile(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 }); try expectEqualStrings("abc\n", aw.written()); aw.clearRetainingCapacity(); - try printLineFromFileAnyOs(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 }); + try printLineFromFile(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 }); try expectEqualStrings("def\n", aw.written()); aw.clearRetainingCapacity(); } } -/// TODO multithreaded awareness -var debug_info_allocator: ?mem.Allocator = null; -var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined; -fn getDebugInfoAllocator() mem.Allocator { - if (debug_info_allocator) |a| return a; - - debug_info_arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator); - const allocator = debug_info_arena_allocator.allocator(); - debug_info_allocator = allocator; - return allocator; +/// The returned allocator should be thread-safe if the compilation is multi-threaded, because +/// multiple threads could capture and/or print stack traces simultaneously. +fn getDebugInfoAllocator() Allocator { + // Allow overriding the debug info allocator by exposing `root.debug.getDebugInfoAllocator`. + if (@hasDecl(root, "debug") and @hasDecl(root.debug, "getDebugInfoAllocator")) { + return root.debug.getDebugInfoAllocator(); + } + // Otherwise, use a global arena backed by the page allocator + const S = struct { + var arena: std.heap.ArenaAllocator = .init(std.heap.page_allocator); + var ts_arena: std.heap.ThreadSafeAllocator = .{ .child_allocator = arena.allocator() }; + }; + return S.ts_arena.allocator(); } /// Whether or not the current target can print useful debug information when a segfault occurs. pub const have_segfault_handling_support = switch (native_os) { + .haiku, .linux, - .macos, + .serenity, + + .dragonfly, + .freebsd, .netbsd, - .solaris, + .openbsd, + + .driverkit, + .ios, + .macos, + .tvos, + .visionos, + .watchos, + .illumos, + .solaris, + .windows, => true, - .freebsd, .openbsd => have_ucontext, else => false, }; @@ -1396,7 +1338,16 @@ pub fn updateSegfaultHandler(act: ?*const posix.Sigaction) void { posix.sigaction(posix.SIG.FPE, act, null); } -/// Attaches a global SIGSEGV handler which calls `@panic("segmentation fault");` +/// Attaches a global handler for several signals which, when triggered, prints output to stderr +/// similar to the default panic handler, with a message containing the type of signal and a stack +/// trace if possible. This implementation does not just call the panic handler, because unwinding +/// the stack (for a stack trace) when a signal is received requires special target-specific logic. +/// +/// The signals for which a handler is installed are: +/// * SIGSEGV (segmentation fault) +/// * SIGILL (illegal instruction) +/// * SIGBUS (bus error) +/// * SIGFPE (arithmetic exception) pub fn attachSegfaultHandler() void { if (!have_segfault_handling_support) { @compileError("segfault handler not supported for this target"); @@ -1430,52 +1381,9 @@ fn resetSegfaultHandler() void { } fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) noreturn { - // Reset to the default handler so that if a segfault happens in this handler it will crash - // the process. Also when this handler returns, the original instruction will be repeated - // and the resulting segfault will crash the process rather than continually dump stack traces. - resetSegfaultHandler(); - - const addr = switch (native_os) { - .linux => @intFromPtr(info.fields.sigfault.addr), - .freebsd, .macos => @intFromPtr(info.addr), - .netbsd => @intFromPtr(info.info.reason.fault.addr), - .openbsd => @intFromPtr(info.data.fault.addr), - .solaris, .illumos => @intFromPtr(info.reason.fault.addr), - else => unreachable, - }; - - const code = if (native_os == .netbsd) info.info.code else info.code; - nosuspend switch (panic_stage) { - 0 => { - panic_stage = 1; - _ = panicking.fetchAdd(1, .seq_cst); - - { - lockStdErr(); - defer unlockStdErr(); - - dumpSegfaultInfoPosix(sig, code, addr, ctx_ptr); - } - - waitForOtherThreadToFinishPanicking(); - }, - else => { - // panic mutex already locked - dumpSegfaultInfoPosix(sig, code, addr, ctx_ptr); - }, - }; - - // We cannot allow the signal handler to return because when it runs the original instruction - // again, the memory may be mapped and undefined behavior would occur rather than repeating - // the segfault. So we simply abort here. - posix.abort(); -} - -fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) void { - const stderr = lockStderrWriter(&.{}); - defer unlockStderrWriter(); - _ = switch (sig) { - posix.SIG.SEGV => if (native_arch == .x86_64 and native_os == .linux and code == 128) // SI_KERNEL + if (use_trap_panic) @trap(); + const addr: ?usize, const name: []const u8 = info: { + if (native_os == .linux and native_arch == .x86_64) { // x86_64 doesn't have a full 64-bit virtual address space. // Addresses outside of that address space are non-canonical // and the CPU won't provide the faulting address to us. @@ -1483,100 +1391,107 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) // but can also happen when no addressable memory is involved; // for example when reading/writing model-specific registers // by executing `rdmsr` or `wrmsr` in user-space (unprivileged mode). - stderr.writeAll("General protection exception (no address available)\n") - else - stderr.print("Segmentation fault at address 0x{x}\n", .{addr}), - posix.SIG.ILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}), - posix.SIG.BUS => stderr.print("Bus error at address 0x{x}\n", .{addr}), - posix.SIG.FPE => stderr.print("Arithmetic exception at address 0x{x}\n", .{addr}), - else => unreachable, - } catch posix.abort(); - - switch (native_arch) { - .x86, - .x86_64, - .arm, - .armeb, - .thumb, - .thumbeb, - .aarch64, - .aarch64_be, - => { - // Some kernels don't align `ctx_ptr` properly. Handle this defensively. - const ctx: *align(1) posix.ucontext_t = @ptrCast(ctx_ptr); - var new_ctx: posix.ucontext_t = ctx.*; - if (builtin.os.tag.isDarwin() and builtin.cpu.arch == .aarch64) { - // The kernel incorrectly writes the contents of `__mcontext_data` right after `mcontext`, - // rather than after the 8 bytes of padding that are supposed to sit between the two. Copy the - // contents to the right place so that the `mcontext` pointer will be correct after the - // `relocateContext` call below. - new_ctx.__mcontext_data = @as(*align(1) extern struct { - onstack: c_int, - sigmask: std.c.sigset_t, - stack: std.c.stack_t, - link: ?*std.c.ucontext_t, - mcsize: u64, - mcontext: *std.c.mcontext_t, - __mcontext_data: std.c.mcontext_t align(@sizeOf(usize)), // Disable padding after `mcontext`. - }, @ptrCast(ctx)).__mcontext_data; + const SI_KERNEL = 0x80; + if (sig == posix.SIG.SEGV and info.code == SI_KERNEL) { + break :info .{ null, "General protection exception" }; } - relocateContext(&new_ctx); - dumpStackTraceFromBase(&new_ctx, stderr); - }, - else => {}, - } + } + const addr: usize = switch (native_os) { + .serenity, + .dragonfly, + .freebsd, + .driverkit, + .ios, + .macos, + .tvos, + .visionos, + .watchos, + => @intFromPtr(info.addr), + .linux, + => @intFromPtr(info.fields.sigfault.addr), + .netbsd, + => @intFromPtr(info.info.reason.fault.addr), + .haiku, + .openbsd, + => @intFromPtr(info.data.fault.addr), + .illumos, + .solaris, + => @intFromPtr(info.reason.fault.addr), + else => comptime unreachable, + }; + const name = switch (sig) { + posix.SIG.SEGV => "Segmentation fault", + posix.SIG.ILL => "Illegal instruction", + posix.SIG.BUS => "Bus error", + posix.SIG.FPE => "Arithmetic exception", + else => unreachable, + }; + break :info .{ addr, name }; + }; + const opt_cpu_context: ?cpu_context.Native = cpu_context.fromPosixSignalContext(ctx_ptr); + handleSegfault(addr, name, if (opt_cpu_context) |*ctx| ctx else null); } fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_long { - switch (info.ExceptionRecord.ExceptionCode) { - windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, 0, "Unaligned Memory Access"), - windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, 1, null), - windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, 2, null), - windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, 0, "Stack Overflow"), + if (use_trap_panic) @trap(); + const name: []const u8, const addr: ?usize = switch (info.ExceptionRecord.ExceptionCode) { + windows.EXCEPTION_DATATYPE_MISALIGNMENT => .{ "Unaligned memory access", null }, + windows.EXCEPTION_ACCESS_VIOLATION => .{ "Segmentation fault", info.ExceptionRecord.ExceptionInformation[1] }, + windows.EXCEPTION_ILLEGAL_INSTRUCTION => .{ "Illegal instruction", info.ContextRecord.getRegs().ip }, + windows.EXCEPTION_STACK_OVERFLOW => .{ "Stack overflow", null }, else => return windows.EXCEPTION_CONTINUE_SEARCH, - } + }; + handleSegfault(addr, name, &cpu_context.fromWindowsContext(info.ContextRecord)); } -fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) noreturn { - // For backends that cannot handle the language features used by this segfault handler, we have a simpler one, - switch (builtin.zig_backend) { - .stage2_x86_64 => if (builtin.target.ofmt == .coff) @trap(), - else => {}, +fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noreturn { + // Allow overriding the target-agnostic segfault handler by exposing `root.debug.handleSegfault`. + if (@hasDecl(root, "debug") and @hasDecl(root.debug, "handleSegfault")) { + return root.debug.handleSegfault(addr, name, opt_ctx); } + return defaultHandleSegfault(addr, name, opt_ctx); +} - comptime assert(windows.CONTEXT != void); - nosuspend switch (panic_stage) { +pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noreturn { + // There is very similar logic to the following in `defaultPanic`. + switch (panic_stage) { 0 => { panic_stage = 1; _ = panicking.fetchAdd(1, .seq_cst); - { + trace: { + const tty_config = tty.detectConfig(.stderr()); + const stderr = lockStderrWriter(&.{}); defer unlockStderrWriter(); - dumpSegfaultInfoWindows(info, msg, label, stderr); + if (addr) |a| { + stderr.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace; + } else { + stderr.print("{s} (no address available)\n", .{name}) catch break :trace; + } + if (opt_ctx) |context| { + writeCurrentStackTrace(.{ + .context = context, + .allow_unsafe_unwind = true, // we're crashing anyway, give it our all! + }, stderr, tty_config) catch break :trace; + } } - - waitForOtherThreadToFinishPanicking(); }, 1 => { panic_stage = 2; + // A segfault happened while trying to print a previous panic message. + // We're still holding the mutex but that's fine as we're going to + // call abort(). fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {}; }, - else => {}, - }; - posix.abort(); -} - -fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8, stderr: *Writer) void { - _ = switch (msg) { - 0 => stderr.print("{s}\n", .{label.?}), - 1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}), - 2 => stderr.print("Illegal instruction at address 0x{x}\n", .{info.ContextRecord.getRegs().ip}), - else => unreachable, - } catch posix.abort(); + else => {}, // Panicked while printing the recursive panic message. + } - dumpStackTraceFromBase(info.ContextRecord, stderr); + // We cannot allow the signal handler to return because when it runs the original instruction + // again, the memory may be mapped and undefined behavior would occur rather than repeating + // the segfault. So we simply abort here. + posix.abort(); } pub fn dumpStackPointerAddr(prefix: []const u8) void { @@ -1587,26 +1502,23 @@ pub fn dumpStackPointerAddr(prefix: []const u8) void { } test "manage resources correctly" { - if (builtin.strip_debug_info) return error.SkipZigTest; - - if (native_os == .wasi) return error.SkipZigTest; - - if (native_os == .windows) { - // https://github.com/ziglang/zig/issues/13963 - return error.SkipZigTest; - } - - // self-hosted debug info is still too buggy - if (builtin.zig_backend != .stage2_llvm) return error.SkipZigTest; - - var discarding: Writer.Discarding = .init(&.{}); - var di = try SelfInfo.open(testing.allocator); - defer di.deinit(); - try printSourceAtAddress(&di, &discarding.writer, showMyTrace(), tty.detectConfig(.stderr())); -} - -noinline fn showMyTrace() usize { - return @returnAddress(); + if (SelfInfo == void) return error.SkipZigTest; + const S = struct { + noinline fn showMyTrace() usize { + return @returnAddress(); + } + }; + const gpa = std.testing.allocator; + var discarding: std.Io.Writer.Discarding = .init(&.{}); + var di: SelfInfo = .init; + defer di.deinit(gpa); + try printSourceAtAddress( + gpa, + &di, + &discarding.writer, + S.showMyTrace(), + tty.detectConfig(.stderr()), + ); } /// This API helps you track where a value originated and where it was mutated, @@ -1653,12 +1565,11 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize if (t.index < size) { t.notes[t.index] = note; - t.addrs[t.index] = [1]usize{0} ** stack_frame_count; - var stack_trace: std.builtin.StackTrace = .{ - .index = 0, - .instruction_addresses = &t.addrs[t.index], - }; - captureStackTrace(addr, &stack_trace); + const addrs = &t.addrs[t.index]; + const st = captureCurrentStackTrace(.{ .first_address = addr }, addrs); + if (st.index < addrs.len) { + @memset(addrs[st.index..], 0); // zero unused frames to indicate end of trace + } } // Keep counting even if the end is reached so that the // user can find out how much more size they need. @@ -1672,13 +1583,6 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize const stderr = lockStderrWriter(&.{}); defer unlockStderrWriter(); const end = @min(t.index, size); - const debug_info = getSelfDebugInfo() catch |err| { - stderr.print( - "Unable to dump stack trace: Unable to open debug info: {s}\n", - .{@errorName(err)}, - ) catch return; - return; - }; for (t.addrs[0..end], 0..) |frames_array, i| { stderr.print("{s}:\n", .{t.notes[i]}) catch return; var frames_array_mutable = frames_array; @@ -1687,7 +1591,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize .index = frames.len, .instruction_addresses = frames, }; - writeStackTrace(stack_trace, stderr, debug_info, tty_config) catch continue; + writeStackTrace(&stack_trace, stderr, tty_config) catch return; } if (t.index > end) { stderr.print("{d} more traces not shown; consider increasing trace size\n", .{ diff --git a/lib/std/debug/Coverage.zig b/lib/std/debug/Coverage.zig index 58e600dc63..f1621c0e12 100644 --- a/lib/std/debug/Coverage.zig +++ b/lib/std/debug/Coverage.zig @@ -145,6 +145,7 @@ pub const ResolveAddressesDwarfError = Dwarf.ScanError; pub fn resolveAddressesDwarf( cov: *Coverage, gpa: Allocator, + endian: std.builtin.Endian, /// Asserts the addresses are in ascending order. sorted_pc_addrs: []const u64, /// Asserts its length equals length of `sorted_pc_addrs`. @@ -184,7 +185,7 @@ pub fn resolveAddressesDwarf( if (cu.src_loc_cache == null) { cov.mutex.unlock(); defer cov.mutex.lock(); - d.populateSrcLocCache(gpa, cu) catch |err| switch (err) { + d.populateSrcLocCache(gpa, endian, cu) catch |err| switch (err) { error.MissingDebugInfo, error.InvalidDebugInfo => { out.* = SourceLocation.invalid; continue :next_pc; diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig index 5f448075a8..98d7addb32 100644 --- a/lib/std/debug/Dwarf.zig +++ b/lib/std/debug/Dwarf.zig @@ -1,22 +1,18 @@ //! Implements parsing, decoding, and caching of DWARF information. //! -//! This API does not assume the current executable is itself the thing being -//! debugged, however, it does assume the debug info has the same CPU -//! architecture and OS as the current executable. It is planned to remove this -//! limitation. +//! This API makes no assumptions about the relationship between the host and +//! the target being debugged. In other words, any DWARF information can be used +//! from any host via this API. Note, however, that the limits of 32-bit +//! addressing can cause very large 64-bit binaries to be impossible to open on +//! 32-bit hosts. //! //! For unopinionated types and bits, see `std.dwarf`. -const builtin = @import("builtin"); -const native_endian = builtin.cpu.arch.endian(); - const std = @import("../std.zig"); const Allocator = std.mem.Allocator; -const elf = std.elf; const mem = std.mem; const DW = std.dwarf; const AT = DW.AT; -const EH = DW.EH; const FORM = DW.FORM; const Format = DW.Format; const RLE = DW.RLE; @@ -24,7 +20,6 @@ const UT = DW.UT; const assert = std.debug.assert; const cast = std.math.cast; const maxInt = std.math.maxInt; -const Path = std.Build.Cache.Path; const ArrayList = std.ArrayList; const Endian = std.builtin.Endian; const Reader = std.Io.Reader; @@ -32,15 +27,13 @@ const Reader = std.Io.Reader; const Dwarf = @This(); pub const expression = @import("Dwarf/expression.zig"); -pub const abi = @import("Dwarf/abi.zig"); -pub const call_frame = @import("Dwarf/call_frame.zig"); +pub const Unwind = @import("Dwarf/Unwind.zig"); +pub const SelfUnwinder = @import("Dwarf/SelfUnwinder.zig"); /// Useful to temporarily enable while working on this file. const debug_debug_mode = false; -endian: Endian, -sections: SectionArray = null_section_array, -is_macho: bool, +sections: SectionArray = @splat(null), /// Filled later by the initializer abbrev_table_list: ArrayList(Abbrev.Table) = .empty, @@ -49,14 +42,6 @@ compile_unit_list: ArrayList(CompileUnit) = .empty, /// Filled later by the initializer func_list: ArrayList(Func) = .empty, -/// Starts out non-`null` if the `.eh_frame_hdr` section is present. May become `null` later if we -/// find that `.eh_frame_hdr` is incomplete. -eh_frame_hdr: ?ExceptionFrameHeader = null, -/// These lookup tables are only used if `eh_frame_hdr` is null -cie_map: std.AutoArrayHashMapUnmanaged(u64, CommonInformationEntry) = .empty, -/// Sorted by start_pc -fde_list: ArrayList(FrameDescriptionEntry) = .empty, - /// Populated by `populateRanges`. ranges: ArrayList(Range) = .empty, @@ -69,10 +54,7 @@ pub const Range = struct { pub const Section = struct { data: []const u8, - // Module-relative virtual address. - // Only set if the section data was loaded from disk. - virtual_address: ?usize = null, - // If `data` is owned by this Dwarf. + /// If `data` is owned by this Dwarf. owned: bool, pub const Id = enum { @@ -87,21 +69,7 @@ pub const Section = struct { debug_rnglists, debug_addr, debug_names, - debug_frame, - eh_frame, - eh_frame_hdr, }; - - // For sections that are not memory mapped by the loader, this is an offset - // from `data.ptr` to where the section would have been mapped. Otherwise, - // `data` is directly backed by the section and the offset is zero. - pub fn virtualOffset(self: Section, base_address: usize) i64 { - return if (self.virtual_address) |va| - @as(i64, @intCast(base_address + va)) - - @as(i64, @intCast(@intFromPtr(self.data.ptr))) - else - 0; - } }; pub const Abbrev = struct { @@ -110,8 +78,8 @@ pub const Abbrev = struct { has_children: bool, attrs: []Attr, - fn deinit(abbrev: *Abbrev, allocator: Allocator) void { - allocator.free(abbrev.attrs); + fn deinit(abbrev: *Abbrev, gpa: Allocator) void { + gpa.free(abbrev.attrs); abbrev.* = undefined; } @@ -127,11 +95,11 @@ pub const Abbrev = struct { offset: u64, abbrevs: []Abbrev, - fn deinit(table: *Table, allocator: Allocator) void { + fn deinit(table: *Table, gpa: Allocator) void { for (table.abbrevs) |*abbrev| { - abbrev.deinit(allocator); + abbrev.deinit(gpa); } - allocator.free(table.abbrevs); + gpa.free(table.abbrevs); table.* = undefined; } @@ -146,6 +114,7 @@ pub const Abbrev = struct { pub const CompileUnit = struct { version: u16, format: Format, + addr_size_bytes: u8, die: Die, pc_range: ?PcRange, @@ -196,7 +165,7 @@ pub const CompileUnit = struct { pub const FormValue = union(enum) { addr: u64, - addrx: usize, + addrx: u64, block: []const u8, udata: u64, data16: *const [16]u8, @@ -208,7 +177,7 @@ pub const FormValue = union(enum) { ref_addr: u64, string: [:0]const u8, strp: u64, - strx: usize, + strx: u64, line_strp: u64, loclistx: u64, rnglistx: u64, @@ -243,8 +212,8 @@ pub const Die = struct { value: FormValue, }; - fn deinit(self: *Die, allocator: Allocator) void { - allocator.free(self.attrs); + fn deinit(self: *Die, gpa: Allocator) void { + gpa.free(self.attrs); self.* = undefined; } @@ -258,13 +227,14 @@ pub const Die = struct { fn getAttrAddr( self: *const Die, di: *const Dwarf, + endian: Endian, id: u64, - compile_unit: CompileUnit, + compile_unit: *const CompileUnit, ) error{ InvalidDebugInfo, MissingDebugInfo }!u64 { const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; return switch (form_value.*) { .addr => |value| value, - .addrx => |index| di.readDebugAddr(compile_unit, index), + .addrx => |index| di.readDebugAddr(endian, compile_unit, index), else => bad(), }; } @@ -294,9 +264,10 @@ pub const Die = struct { pub fn getAttrString( self: *const Die, di: *Dwarf, + endian: Endian, id: u64, opt_str: ?[]const u8, - compile_unit: CompileUnit, + compile_unit: *const CompileUnit, ) error{ InvalidDebugInfo, MissingDebugInfo }![]const u8 { const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; switch (form_value.*) { @@ -309,13 +280,13 @@ pub const Die = struct { .@"32" => { const byte_offset = compile_unit.str_offsets_base + 4 * index; if (byte_offset + 4 > debug_str_offsets.len) return bad(); - const offset = mem.readInt(u32, debug_str_offsets[byte_offset..][0..4], di.endian); + const offset = mem.readInt(u32, debug_str_offsets[@intCast(byte_offset)..][0..4], endian); return getStringGeneric(opt_str, offset); }, .@"64" => { const byte_offset = compile_unit.str_offsets_base + 8 * index; if (byte_offset + 8 > debug_str_offsets.len) return bad(); - const offset = mem.readInt(u64, debug_str_offsets[byte_offset..][0..8], di.endian); + const offset = mem.readInt(u64, debug_str_offsets[@intCast(byte_offset)..][0..8], endian); return getStringGeneric(opt_str, offset); }, } @@ -326,440 +297,17 @@ pub const Die = struct { } }; -/// This represents the decoded .eh_frame_hdr header -pub const ExceptionFrameHeader = struct { - eh_frame_ptr: usize, - table_enc: u8, - fde_count: usize, - entries: []const u8, - - pub fn entrySize(table_enc: u8) !u8 { - return switch (table_enc & EH.PE.type_mask) { - EH.PE.udata2, - EH.PE.sdata2, - => 4, - EH.PE.udata4, - EH.PE.sdata4, - => 8, - EH.PE.udata8, - EH.PE.sdata8, - => 16, - // This is a binary search table, so all entries must be the same length - else => return bad(), - }; - } - - pub fn findEntry( - self: ExceptionFrameHeader, - eh_frame_len: usize, - eh_frame_hdr_ptr: usize, - pc: usize, - cie: *CommonInformationEntry, - fde: *FrameDescriptionEntry, - endian: Endian, - ) !void { - const entry_size = try entrySize(self.table_enc); - - var left: usize = 0; - var len: usize = self.fde_count; - var fbr: Reader = .fixed(self.entries); - - while (len > 1) { - const mid = left + len / 2; - - fbr.seek = mid * entry_size; - const pc_begin = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ - .pc_rel_base = @intFromPtr(&self.entries[fbr.seek]), - .follow_indirect = true, - .data_rel_base = eh_frame_hdr_ptr, - }, endian) orelse return bad(); - - if (pc < pc_begin) { - len /= 2; - } else { - left = mid; - if (pc == pc_begin) break; - len -= len / 2; - } - } - - if (len == 0) return missing(); - fbr.seek = left * entry_size; - - // Read past the pc_begin field of the entry - _ = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ - .pc_rel_base = @intFromPtr(&self.entries[fbr.seek]), - .follow_indirect = true, - .data_rel_base = eh_frame_hdr_ptr, - }, endian) orelse return bad(); - - const fde_ptr = cast(usize, try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ - .pc_rel_base = @intFromPtr(&self.entries[fbr.seek]), - .follow_indirect = true, - .data_rel_base = eh_frame_hdr_ptr, - }, endian) orelse return bad()) orelse return bad(); - - if (fde_ptr < self.eh_frame_ptr) return bad(); - - const eh_frame = @as([*]const u8, @ptrFromInt(self.eh_frame_ptr))[0..eh_frame_len]; - - const fde_offset = fde_ptr - self.eh_frame_ptr; - var eh_frame_fbr: Reader = .fixed(eh_frame); - eh_frame_fbr.seek = fde_offset; - - const fde_entry_header = try EntryHeader.read(&eh_frame_fbr, .eh_frame, endian); - if (fde_entry_header.type != .fde) return bad(); - - // CIEs always come before FDEs (the offset is a subtraction), so we can assume this memory is readable - const cie_offset = fde_entry_header.type.fde; - eh_frame_fbr.seek = @intCast(cie_offset); - const cie_entry_header = try EntryHeader.read(&eh_frame_fbr, .eh_frame, endian); - if (cie_entry_header.type != .cie) return bad(); - - cie.* = try CommonInformationEntry.parse( - cie_entry_header.entry_bytes, - 0, - true, - cie_entry_header.format, - .eh_frame, - cie_entry_header.length_offset, - @sizeOf(usize), - endian, - ); - - fde.* = try FrameDescriptionEntry.parse( - fde_entry_header.entry_bytes, - 0, - true, - cie.*, - @sizeOf(usize), - endian, - ); - - if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return missing(); - } -}; - -pub const EntryHeader = struct { - /// Offset of the length field in the backing buffer - length_offset: usize, - format: Format, - type: union(enum) { - cie, - /// Value is the offset of the corresponding CIE - fde: u64, - terminator, - }, - /// The entry's contents, not including the ID field - entry_bytes: []const u8, - - /// The length of the entry including the ID field, but not the length field itself - pub fn entryLength(self: EntryHeader) usize { - return self.entry_bytes.len + @as(u8, if (self.format == .@"64") 8 else 4); - } - - /// Reads a header for either an FDE or a CIE, then advances the fbr to the - /// position after the trailing structure. - /// - /// `fbr` must be backed by either the .eh_frame or .debug_frame sections. - /// - /// TODO that's a bad API, don't do that. this function should neither require - /// a fixed reader nor depend on seeking. - pub fn read(fbr: *Reader, dwarf_section: Section.Id, endian: Endian) !EntryHeader { - assert(dwarf_section == .eh_frame or dwarf_section == .debug_frame); - - const length_offset = fbr.seek; - const unit_header = try readUnitHeader(fbr, endian); - const unit_length = cast(usize, unit_header.unit_length) orelse return bad(); - if (unit_length == 0) return .{ - .length_offset = length_offset, - .format = unit_header.format, - .type = .terminator, - .entry_bytes = &.{}, - }; - const start_offset = fbr.seek; - const end_offset = start_offset + unit_length; - defer fbr.seek = end_offset; - - const id = try readAddress(fbr, unit_header.format, endian); - const entry_bytes = fbr.buffer[fbr.seek..end_offset]; - const cie_id: u64 = switch (dwarf_section) { - .eh_frame => CommonInformationEntry.eh_id, - .debug_frame => switch (unit_header.format) { - .@"32" => CommonInformationEntry.dwarf32_id, - .@"64" => CommonInformationEntry.dwarf64_id, - }, - else => unreachable, - }; - - return .{ - .length_offset = length_offset, - .format = unit_header.format, - .type = if (id == cie_id) .cie else .{ .fde = switch (dwarf_section) { - .eh_frame => try std.math.sub(u64, start_offset, id), - .debug_frame => id, - else => unreachable, - } }, - .entry_bytes = entry_bytes, - }; - } -}; - -pub const CommonInformationEntry = struct { - // Used in .eh_frame - pub const eh_id = 0; - - // Used in .debug_frame (DWARF32) - pub const dwarf32_id = maxInt(u32); - - // Used in .debug_frame (DWARF64) - pub const dwarf64_id = maxInt(u64); - - // Offset of the length field of this entry in the eh_frame section. - // This is the key that FDEs use to reference CIEs. - length_offset: u64, - version: u8, - address_size: u8, - format: Format, - - // Only present in version 4 - segment_selector_size: ?u8, - - code_alignment_factor: u32, - data_alignment_factor: i32, - return_address_register: u8, - - aug_str: []const u8, - aug_data: []const u8, - lsda_pointer_enc: u8, - personality_enc: ?u8, - personality_routine_pointer: ?u64, - fde_pointer_enc: u8, - initial_instructions: []const u8, - - pub fn isSignalFrame(self: CommonInformationEntry) bool { - for (self.aug_str) |c| if (c == 'S') return true; - return false; - } - - pub fn addressesSignedWithBKey(self: CommonInformationEntry) bool { - for (self.aug_str) |c| if (c == 'B') return true; - return false; - } - - pub fn mteTaggedFrame(self: CommonInformationEntry) bool { - for (self.aug_str) |c| if (c == 'G') return true; - return false; - } - - /// This function expects to read the CIE starting with the version field. - /// The returned struct references memory backed by cie_bytes. - /// - /// See the FrameDescriptionEntry.parse documentation for the description - /// of `pc_rel_offset` and `is_runtime`. - /// - /// `length_offset` specifies the offset of this CIE's length field in the - /// .eh_frame / .debug_frame section. - pub fn parse( - cie_bytes: []const u8, - pc_rel_offset: i64, - is_runtime: bool, - format: Format, - dwarf_section: Section.Id, - length_offset: u64, - addr_size_bytes: u8, - endian: Endian, - ) !CommonInformationEntry { - if (addr_size_bytes > 8) return error.UnsupportedAddrSize; - - var fbr: Reader = .fixed(cie_bytes); - - const version = try fbr.takeByte(); - switch (dwarf_section) { - .eh_frame => if (version != 1 and version != 3) return error.UnsupportedDwarfVersion, - .debug_frame => if (version != 4) return error.UnsupportedDwarfVersion, - else => return error.UnsupportedDwarfSection, - } - - var has_eh_data = false; - var has_aug_data = false; - - var aug_str_len: usize = 0; - const aug_str_start = fbr.seek; - var aug_byte = try fbr.takeByte(); - while (aug_byte != 0) : (aug_byte = try fbr.takeByte()) { - switch (aug_byte) { - 'z' => { - if (aug_str_len != 0) return bad(); - has_aug_data = true; - }, - 'e' => { - if (has_aug_data or aug_str_len != 0) return bad(); - if (try fbr.takeByte() != 'h') return bad(); - has_eh_data = true; - }, - else => if (has_eh_data) return bad(), - } - - aug_str_len += 1; - } - - if (has_eh_data) { - // legacy data created by older versions of gcc - unsupported here - for (0..addr_size_bytes) |_| _ = try fbr.takeByte(); - } - - const address_size = if (version == 4) try fbr.takeByte() else addr_size_bytes; - const segment_selector_size = if (version == 4) try fbr.takeByte() else null; - - const code_alignment_factor = try fbr.takeLeb128(u32); - const data_alignment_factor = try fbr.takeLeb128(i32); - const return_address_register = if (version == 1) try fbr.takeByte() else try fbr.takeLeb128(u8); - - var lsda_pointer_enc: u8 = EH.PE.omit; - var personality_enc: ?u8 = null; - var personality_routine_pointer: ?u64 = null; - var fde_pointer_enc: u8 = EH.PE.absptr; - - var aug_data: []const u8 = &[_]u8{}; - const aug_str = if (has_aug_data) blk: { - const aug_data_len = try fbr.takeLeb128(usize); - const aug_data_start = fbr.seek; - aug_data = cie_bytes[aug_data_start..][0..aug_data_len]; - - const aug_str = cie_bytes[aug_str_start..][0..aug_str_len]; - for (aug_str[1..]) |byte| { - switch (byte) { - 'L' => { - lsda_pointer_enc = try fbr.takeByte(); - }, - 'P' => { - personality_enc = try fbr.takeByte(); - personality_routine_pointer = try readEhPointer(&fbr, personality_enc.?, addr_size_bytes, .{ - .pc_rel_base = try pcRelBase(@intFromPtr(&cie_bytes[fbr.seek]), pc_rel_offset), - .follow_indirect = is_runtime, - }, endian); - }, - 'R' => { - fde_pointer_enc = try fbr.takeByte(); - }, - 'S', 'B', 'G' => {}, - else => return bad(), - } - } - - // aug_data_len can include padding so the CIE ends on an address boundary - fbr.seek = aug_data_start + aug_data_len; - break :blk aug_str; - } else &[_]u8{}; - - const initial_instructions = cie_bytes[fbr.seek..]; - return .{ - .length_offset = length_offset, - .version = version, - .address_size = address_size, - .format = format, - .segment_selector_size = segment_selector_size, - .code_alignment_factor = code_alignment_factor, - .data_alignment_factor = data_alignment_factor, - .return_address_register = return_address_register, - .aug_str = aug_str, - .aug_data = aug_data, - .lsda_pointer_enc = lsda_pointer_enc, - .personality_enc = personality_enc, - .personality_routine_pointer = personality_routine_pointer, - .fde_pointer_enc = fde_pointer_enc, - .initial_instructions = initial_instructions, - }; - } -}; - -pub const FrameDescriptionEntry = struct { - // Offset into eh_frame where the CIE for this FDE is stored - cie_length_offset: u64, - - pc_begin: u64, - pc_range: u64, - lsda_pointer: ?u64, - aug_data: []const u8, - instructions: []const u8, - - /// This function expects to read the FDE starting at the PC Begin field. - /// The returned struct references memory backed by `fde_bytes`. - /// - /// `pc_rel_offset` specifies an offset to be applied to pc_rel_base values - /// used when decoding pointers. This should be set to zero if fde_bytes is - /// backed by the memory of a .eh_frame / .debug_frame section in the running executable. - /// Otherwise, it should be the relative offset to translate addresses from - /// where the section is currently stored in memory, to where it *would* be - /// stored at runtime: section base addr - backing data base ptr. - /// - /// Similarly, `is_runtime` specifies this function is being called on a runtime - /// section, and so indirect pointers can be followed. - pub fn parse( - fde_bytes: []const u8, - pc_rel_offset: i64, - is_runtime: bool, - cie: CommonInformationEntry, - addr_size_bytes: u8, - endian: Endian, - ) !FrameDescriptionEntry { - if (addr_size_bytes > 8) return error.InvalidAddrSize; - - var fbr: Reader = .fixed(fde_bytes); - - const pc_begin = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{ - .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.seek]), pc_rel_offset), - .follow_indirect = is_runtime, - }, endian) orelse return bad(); - - const pc_range = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{ - .pc_rel_base = 0, - .follow_indirect = false, - }, endian) orelse return bad(); - - var aug_data: []const u8 = &[_]u8{}; - const lsda_pointer = if (cie.aug_str.len > 0) blk: { - const aug_data_len = try fbr.takeLeb128(usize); - const aug_data_start = fbr.seek; - aug_data = fde_bytes[aug_data_start..][0..aug_data_len]; - - const lsda_pointer = if (cie.lsda_pointer_enc != EH.PE.omit) - try readEhPointer(&fbr, cie.lsda_pointer_enc, addr_size_bytes, .{ - .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.seek]), pc_rel_offset), - .follow_indirect = is_runtime, - }, endian) - else - null; - - fbr.seek = aug_data_start + aug_data_len; - break :blk lsda_pointer; - } else null; - - const instructions = fde_bytes[fbr.seek..]; - return .{ - .cie_length_offset = cie.length_offset, - .pc_begin = pc_begin, - .pc_range = pc_range, - .lsda_pointer = lsda_pointer, - .aug_data = aug_data, - .instructions = instructions, - }; - } -}; - const num_sections = std.enums.directEnumArrayLen(Section.Id, 0); pub const SectionArray = [num_sections]?Section; -pub const null_section_array = [_]?Section{null} ** num_sections; pub const OpenError = ScanError; /// Initialize DWARF info. The caller has the responsibility to initialize most /// the `Dwarf` fields before calling. `binary_mem` is the raw bytes of the /// main binary file (not the secondary debug info file). -pub fn open(d: *Dwarf, gpa: Allocator) OpenError!void { - try d.scanAllFunctions(gpa); - try d.scanAllCompileUnits(gpa); +pub fn open(d: *Dwarf, gpa: Allocator, endian: Endian) OpenError!void { + try d.scanAllFunctions(gpa, endian); + try d.scanAllCompileUnits(gpa, endian); } const PcRange = struct { @@ -776,10 +324,6 @@ pub fn section(di: Dwarf, dwarf_section: Section.Id) ?[]const u8 { return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.data else null; } -pub fn sectionVirtualOffset(di: Dwarf, dwarf_section: Section.Id, base_address: usize) ?i64 { - return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.virtualOffset(base_address) else null; -} - pub fn deinit(di: *Dwarf, gpa: Allocator) void { for (di.sections) |opt_section| { if (opt_section) |s| if (s.owned) gpa.free(s.data); @@ -798,14 +342,18 @@ pub fn deinit(di: *Dwarf, gpa: Allocator) void { } di.compile_unit_list.deinit(gpa); di.func_list.deinit(gpa); - di.cie_map.deinit(gpa); - di.fde_list.deinit(gpa); di.ranges.deinit(gpa); di.* = undefined; } -pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 { - for (di.func_list.items) |*func| { +pub fn getSymbolName(di: *const Dwarf, address: u64) ?[]const u8 { + // Iterate the function list backwards so that we see child DIEs before their parents. This is + // important because `DW_TAG_inlined_subroutine` DIEs will have a range which is a sub-range of + // their caller, and we want to return the callee's name, not the caller's. + var i: usize = di.func_list.items.len; + while (i > 0) { + i -= 1; + const func = &di.func_list.items[i]; if (func.pc_range) |range| { if (address >= range.start and address < range.end) { return func.name; @@ -825,35 +373,33 @@ pub const ScanError = error{ StreamTooLong, } || Allocator.Error; -fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { - const endian = di.endian; - var fbr: Reader = .fixed(di.section(.debug_info).?); +fn scanAllFunctions(di: *Dwarf, gpa: Allocator, endian: Endian) ScanError!void { + var fr: Reader = .fixed(di.section(.debug_info).?); var this_unit_offset: u64 = 0; - while (this_unit_offset < fbr.buffer.len) { - fbr.seek = @intCast(this_unit_offset); + while (this_unit_offset < fr.buffer.len) { + fr.seek = @intCast(this_unit_offset); - const unit_header = try readUnitHeader(&fbr, endian); + const unit_header = try readUnitHeader(&fr, endian); if (unit_header.unit_length == 0) return; const next_offset = unit_header.header_length + unit_header.unit_length; - const version = try fbr.takeInt(u16, endian); + const version = try fr.takeInt(u16, endian); if (version < 2 or version > 5) return bad(); var address_size: u8 = undefined; var debug_abbrev_offset: u64 = undefined; if (version >= 5) { - const unit_type = try fbr.takeByte(); + const unit_type = try fr.takeByte(); if (unit_type != DW.UT.compile) return bad(); - address_size = try fbr.takeByte(); - debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian); + address_size = try fr.takeByte(); + debug_abbrev_offset = try readFormatSizedInt(&fr, unit_header.format, endian); } else { - debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian); - address_size = try fbr.takeByte(); + debug_abbrev_offset = try readFormatSizedInt(&fr, unit_header.format, endian); + address_size = try fr.takeByte(); } - if (address_size != @sizeOf(usize)) return bad(); - const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset); + const abbrev_table = try di.getAbbrevTable(gpa, debug_abbrev_offset); var max_attrs: usize = 0; var zig_padding_abbrev_code: u7 = 0; @@ -868,8 +414,8 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { } } } - const attrs_buf = try allocator.alloc(Die.Attr, max_attrs * 3); - defer allocator.free(attrs_buf); + const attrs_buf = try gpa.alloc(Die.Attr, max_attrs * 3); + defer gpa.free(attrs_buf); var attrs_bufs: [3][]Die.Attr = undefined; for (&attrs_bufs, 0..) |*buf, index| buf.* = attrs_buf[index * max_attrs ..][0..max_attrs]; @@ -878,6 +424,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { var compile_unit: CompileUnit = .{ .version = version, .format = unit_header.format, + .addr_size_bytes = address_size, .die = undefined, .pc_range = null, @@ -890,16 +437,17 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { }; while (true) { - fbr.seek = std.mem.indexOfNonePos(u8, fbr.buffer, fbr.seek, &.{ + fr.seek = std.mem.indexOfNonePos(u8, fr.buffer, fr.seek, &.{ zig_padding_abbrev_code, 0, - }) orelse fbr.buffer.len; - if (fbr.seek >= next_unit_pos) break; + }) orelse fr.buffer.len; + if (fr.seek >= next_unit_pos) break; var die_obj = (try parseDie( - &fbr, + &fr, attrs_bufs[0], abbrev_table, unit_header.format, endian, + address_size, )) orelse continue; switch (die_obj.tag_id) { @@ -920,34 +468,36 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { // Prevent endless loops for (0..3) |_| { if (this_die_obj.getAttr(AT.name)) |_| { - break :x try this_die_obj.getAttrString(di, AT.name, di.section(.debug_str), compile_unit); + break :x try this_die_obj.getAttrString(di, endian, AT.name, di.section(.debug_str), &compile_unit); } else if (this_die_obj.getAttr(AT.abstract_origin)) |_| { - const after_die_offset = fbr.seek; - defer fbr.seek = after_die_offset; + const after_die_offset = fr.seek; + defer fr.seek = after_die_offset; // Follow the DIE it points to and repeat const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin, this_unit_offset, next_offset); - fbr.seek = @intCast(ref_offset); + fr.seek = @intCast(ref_offset); this_die_obj = (try parseDie( - &fbr, + &fr, attrs_bufs[2], abbrev_table, // wrong abbrev table for different cu unit_header.format, endian, + address_size, )) orelse return bad(); } else if (this_die_obj.getAttr(AT.specification)) |_| { - const after_die_offset = fbr.seek; - defer fbr.seek = after_die_offset; + const after_die_offset = fr.seek; + defer fr.seek = after_die_offset; // Follow the DIE it points to and repeat const ref_offset = try this_die_obj.getAttrRef(AT.specification, this_unit_offset, next_offset); - fbr.seek = @intCast(ref_offset); + fr.seek = @intCast(ref_offset); this_die_obj = (try parseDie( - &fbr, + &fr, attrs_bufs[2], abbrev_table, // wrong abbrev table for different cu unit_header.format, endian, + address_size, )) orelse return bad(); } else { break :x null; @@ -957,7 +507,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { break :x null; }; - var range_added = if (die_obj.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| blk: { + var range_added = if (die_obj.getAttrAddr(di, endian, AT.low_pc, &compile_unit)) |low_pc| blk: { if (die_obj.getAttr(AT.high_pc)) |high_pc_value| { const pc_end = switch (high_pc_value.*) { .addr => |value| value, @@ -965,7 +515,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { else => return bad(), }; - try di.func_list.append(allocator, .{ + try di.func_list.append(gpa, .{ .name = fn_name, .pc_range = .{ .start = low_pc, @@ -983,14 +533,14 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { }; if (die_obj.getAttr(AT.ranges)) |ranges_value| blk: { - var iter = DebugRangeIterator.init(ranges_value, di, &compile_unit) catch |err| { + var iter = DebugRangeIterator.init(ranges_value, di, endian, &compile_unit) catch |err| { if (err != error.MissingDebugInfo) return err; break :blk; }; while (try iter.next()) |range| { range_added = true; - try di.func_list.append(allocator, .{ + try di.func_list.append(gpa, .{ .name = fn_name, .pc_range = .{ .start = range.start, @@ -1001,7 +551,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { } if (fn_name != null and !range_added) { - try di.func_list.append(allocator, .{ + try di.func_list.append(gpa, .{ .name = fn_name, .pc_range = null, }); @@ -1015,38 +565,36 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void { } } -fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void { - const endian = di.endian; - var fbr: Reader = .fixed(di.section(.debug_info).?); +fn scanAllCompileUnits(di: *Dwarf, gpa: Allocator, endian: Endian) ScanError!void { + var fr: Reader = .fixed(di.section(.debug_info).?); var this_unit_offset: u64 = 0; - var attrs_buf = std.array_list.Managed(Die.Attr).init(allocator); + var attrs_buf = std.array_list.Managed(Die.Attr).init(gpa); defer attrs_buf.deinit(); - while (this_unit_offset < fbr.buffer.len) { - fbr.seek = @intCast(this_unit_offset); + while (this_unit_offset < fr.buffer.len) { + fr.seek = @intCast(this_unit_offset); - const unit_header = try readUnitHeader(&fbr, endian); + const unit_header = try readUnitHeader(&fr, endian); if (unit_header.unit_length == 0) return; const next_offset = unit_header.header_length + unit_header.unit_length; - const version = try fbr.takeInt(u16, endian); + const version = try fr.takeInt(u16, endian); if (version < 2 or version > 5) return bad(); var address_size: u8 = undefined; var debug_abbrev_offset: u64 = undefined; if (version >= 5) { - const unit_type = try fbr.takeByte(); + const unit_type = try fr.takeByte(); if (unit_type != UT.compile) return bad(); - address_size = try fbr.takeByte(); - debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian); + address_size = try fr.takeByte(); + debug_abbrev_offset = try readFormatSizedInt(&fr, unit_header.format, endian); } else { - debug_abbrev_offset = try readAddress(&fbr, unit_header.format, endian); - address_size = try fbr.takeByte(); + debug_abbrev_offset = try readFormatSizedInt(&fr, unit_header.format, endian); + address_size = try fr.takeByte(); } - if (address_size != @sizeOf(usize)) return bad(); - const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset); + const abbrev_table = try di.getAbbrevTable(gpa, debug_abbrev_offset); var max_attrs: usize = 0; for (abbrev_table.abbrevs) |abbrev| { @@ -1055,20 +603,22 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void { try attrs_buf.resize(max_attrs); var compile_unit_die = (try parseDie( - &fbr, + &fr, attrs_buf.items, abbrev_table, unit_header.format, endian, + address_size, )) orelse return bad(); if (compile_unit_die.tag_id != DW.TAG.compile_unit) return bad(); - compile_unit_die.attrs = try allocator.dupe(Die.Attr, compile_unit_die.attrs); + compile_unit_die.attrs = try gpa.dupe(Die.Attr, compile_unit_die.attrs); var compile_unit: CompileUnit = .{ .version = version, .format = unit_header.format, + .addr_size_bytes = address_size, .pc_range = null, .die = compile_unit_die, .str_offsets_base = if (compile_unit_die.getAttr(AT.str_offsets_base)) |fv| try fv.getUInt(usize) else 0, @@ -1080,7 +630,7 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void { }; compile_unit.pc_range = x: { - if (compile_unit_die.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| { + if (compile_unit_die.getAttrAddr(di, endian, AT.low_pc, &compile_unit)) |low_pc| { if (compile_unit_die.getAttr(AT.high_pc)) |high_pc_value| { const pc_end = switch (high_pc_value.*) { .addr => |value| value, @@ -1100,13 +650,13 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void { } }; - try di.compile_unit_list.append(allocator, compile_unit); + try di.compile_unit_list.append(gpa, compile_unit); this_unit_offset += next_offset; } } -pub fn populateRanges(d: *Dwarf, gpa: Allocator) ScanError!void { +pub fn populateRanges(d: *Dwarf, gpa: Allocator, endian: Endian) ScanError!void { assert(d.ranges.items.len == 0); for (d.compile_unit_list.items, 0..) |*cu, cu_index| { @@ -1119,7 +669,7 @@ pub fn populateRanges(d: *Dwarf, gpa: Allocator) ScanError!void { continue; } const ranges_value = cu.die.getAttr(AT.ranges) orelse continue; - var iter = DebugRangeIterator.init(ranges_value, d, cu) catch continue; + var iter = DebugRangeIterator.init(ranges_value, d, endian, cu) catch continue; while (try iter.next()) |range| { // Not sure why LLVM thinks it's OK to emit these... if (range.start == range.end) continue; @@ -1144,10 +694,11 @@ const DebugRangeIterator = struct { base_address: u64, section_type: Section.Id, di: *const Dwarf, + endian: Endian, compile_unit: *const CompileUnit, - fbr: Reader, + fr: Reader, - pub fn init(ranges_value: *const FormValue, di: *const Dwarf, compile_unit: *const CompileUnit) !@This() { + pub fn init(ranges_value: *const FormValue, di: *const Dwarf, endian: Endian, compile_unit: *const CompileUnit) !@This() { const section_type = if (compile_unit.version >= 5) Section.Id.debug_rnglists else Section.Id.debug_ranges; const debug_ranges = di.section(section_type) orelse return error.MissingDebugInfo; @@ -1156,15 +707,15 @@ const DebugRangeIterator = struct { .rnglistx => |idx| off: { switch (compile_unit.format) { .@"32" => { - const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 4 * idx)); + const offset_loc = compile_unit.rnglists_base + 4 * idx; if (offset_loc + 4 > debug_ranges.len) return bad(); - const offset = mem.readInt(u32, debug_ranges[offset_loc..][0..4], di.endian); + const offset = mem.readInt(u32, debug_ranges[@intCast(offset_loc)..][0..4], endian); break :off compile_unit.rnglists_base + offset; }, .@"64" => { - const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 8 * idx)); + const offset_loc = compile_unit.rnglists_base + 8 * idx; if (offset_loc + 8 > debug_ranges.len) return bad(); - const offset = mem.readInt(u64, debug_ranges[offset_loc..][0..8], di.endian); + const offset = mem.readInt(u64, debug_ranges[@intCast(offset_loc)..][0..8], endian); break :off compile_unit.rnglists_base + offset; }, } @@ -1176,42 +727,44 @@ const DebugRangeIterator = struct { // specified by DW_AT.low_pc or to some other value encoded // in the list itself. // If no starting value is specified use zero. - const base_address = compile_unit.die.getAttrAddr(di, AT.low_pc, compile_unit.*) catch |err| switch (err) { + const base_address = compile_unit.die.getAttrAddr(di, endian, AT.low_pc, compile_unit) catch |err| switch (err) { error.MissingDebugInfo => 0, else => return err, }; - var fbr: Reader = .fixed(debug_ranges); - fbr.seek = cast(usize, ranges_offset) orelse return bad(); + var fr: Reader = .fixed(debug_ranges); + fr.seek = cast(usize, ranges_offset) orelse return bad(); return .{ .base_address = base_address, .section_type = section_type, .di = di, + .endian = endian, .compile_unit = compile_unit, - .fbr = fbr, + .fr = fr, }; } // Returns the next range in the list, or null if the end was reached. pub fn next(self: *@This()) !?PcRange { - const endian = self.di.endian; + const endian = self.endian; + const addr_size_bytes = self.compile_unit.addr_size_bytes; switch (self.section_type) { .debug_rnglists => { - const kind = try self.fbr.takeByte(); + const kind = try self.fr.takeByte(); switch (kind) { RLE.end_of_list => return null, RLE.base_addressx => { - const index = try self.fbr.takeLeb128(usize); - self.base_address = try self.di.readDebugAddr(self.compile_unit.*, index); + const index = try self.fr.takeLeb128(u64); + self.base_address = try self.di.readDebugAddr(endian, self.compile_unit, index); return try self.next(); }, RLE.startx_endx => { - const start_index = try self.fbr.takeLeb128(usize); - const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index); + const start_index = try self.fr.takeLeb128(u64); + const start_addr = try self.di.readDebugAddr(endian, self.compile_unit, start_index); - const end_index = try self.fbr.takeLeb128(usize); - const end_addr = try self.di.readDebugAddr(self.compile_unit.*, end_index); + const end_index = try self.fr.takeLeb128(u64); + const end_addr = try self.di.readDebugAddr(endian, self.compile_unit, end_index); return .{ .start = start_addr, @@ -1219,10 +772,10 @@ const DebugRangeIterator = struct { }; }, RLE.startx_length => { - const start_index = try self.fbr.takeLeb128(usize); - const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index); + const start_index = try self.fr.takeLeb128(u64); + const start_addr = try self.di.readDebugAddr(endian, self.compile_unit, start_index); - const len = try self.fbr.takeLeb128(usize); + const len = try self.fr.takeLeb128(u64); const end_addr = start_addr + len; return .{ @@ -1231,8 +784,8 @@ const DebugRangeIterator = struct { }; }, RLE.offset_pair => { - const start_addr = try self.fbr.takeLeb128(usize); - const end_addr = try self.fbr.takeLeb128(usize); + const start_addr = try self.fr.takeLeb128(u64); + const end_addr = try self.fr.takeLeb128(u64); // This is the only kind that uses the base address return .{ @@ -1241,12 +794,12 @@ const DebugRangeIterator = struct { }; }, RLE.base_address => { - self.base_address = try self.fbr.takeInt(usize, endian); + self.base_address = try readAddress(&self.fr, endian, addr_size_bytes); return try self.next(); }, RLE.start_end => { - const start_addr = try self.fbr.takeInt(usize, endian); - const end_addr = try self.fbr.takeInt(usize, endian); + const start_addr = try readAddress(&self.fr, endian, addr_size_bytes); + const end_addr = try readAddress(&self.fr, endian, addr_size_bytes); return .{ .start = start_addr, @@ -1254,8 +807,8 @@ const DebugRangeIterator = struct { }; }, RLE.start_length => { - const start_addr = try self.fbr.takeInt(usize, endian); - const len = try self.fbr.takeLeb128(usize); + const start_addr = try readAddress(&self.fr, endian, addr_size_bytes); + const len = try self.fr.takeLeb128(u64); const end_addr = start_addr + len; return .{ @@ -1267,12 +820,13 @@ const DebugRangeIterator = struct { } }, .debug_ranges => { - const start_addr = try self.fbr.takeInt(usize, endian); - const end_addr = try self.fbr.takeInt(usize, endian); + const start_addr = try readAddress(&self.fr, endian, addr_size_bytes); + const end_addr = try readAddress(&self.fr, endian, addr_size_bytes); if (start_addr == 0 and end_addr == 0) return null; - // This entry selects a new value for the base address - if (start_addr == maxInt(usize)) { + // The entry with start_addr = max_representable_address selects a new value for the base address + const max_representable_address = ~@as(u64, 0) >> @intCast(64 - addr_size_bytes); + if (start_addr == max_representable_address) { self.base_address = end_addr; return try self.next(); } @@ -1288,14 +842,14 @@ const DebugRangeIterator = struct { }; /// TODO: change this to binary searching the sorted compile unit list -pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*CompileUnit { +pub fn findCompileUnit(di: *const Dwarf, endian: Endian, target_address: u64) !*CompileUnit { for (di.compile_unit_list.items) |*compile_unit| { if (compile_unit.pc_range) |range| { if (target_address >= range.start and target_address < range.end) return compile_unit; } const ranges_value = compile_unit.die.getAttr(AT.ranges) orelse continue; - var iter = DebugRangeIterator.init(ranges_value, di, compile_unit) catch continue; + var iter = DebugRangeIterator.init(ranges_value, di, endian, compile_unit) catch continue; while (try iter.next()) |range| { if (target_address >= range.start and target_address < range.end) return compile_unit; } @@ -1306,49 +860,49 @@ pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*CompileUnit { /// Gets an already existing AbbrevTable given the abbrev_offset, or if not found, /// seeks in the stream and parses it. -fn getAbbrevTable(di: *Dwarf, allocator: Allocator, abbrev_offset: u64) !*const Abbrev.Table { +fn getAbbrevTable(di: *Dwarf, gpa: Allocator, abbrev_offset: u64) !*const Abbrev.Table { for (di.abbrev_table_list.items) |*table| { if (table.offset == abbrev_offset) { return table; } } try di.abbrev_table_list.append( - allocator, - try di.parseAbbrevTable(allocator, abbrev_offset), + gpa, + try di.parseAbbrevTable(gpa, abbrev_offset), ); return &di.abbrev_table_list.items[di.abbrev_table_list.items.len - 1]; } -fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table { - var fbr: Reader = .fixed(di.section(.debug_abbrev).?); - fbr.seek = cast(usize, offset) orelse return bad(); +fn parseAbbrevTable(di: *Dwarf, gpa: Allocator, offset: u64) !Abbrev.Table { + var fr: Reader = .fixed(di.section(.debug_abbrev).?); + fr.seek = cast(usize, offset) orelse return bad(); - var abbrevs = std.array_list.Managed(Abbrev).init(allocator); + var abbrevs = std.array_list.Managed(Abbrev).init(gpa); defer { for (abbrevs.items) |*abbrev| { - abbrev.deinit(allocator); + abbrev.deinit(gpa); } abbrevs.deinit(); } - var attrs = std.array_list.Managed(Abbrev.Attr).init(allocator); + var attrs = std.array_list.Managed(Abbrev.Attr).init(gpa); defer attrs.deinit(); while (true) { - const code = try fbr.takeLeb128(u64); + const code = try fr.takeLeb128(u64); if (code == 0) break; - const tag_id = try fbr.takeLeb128(u64); - const has_children = (try fbr.takeByte()) == DW.CHILDREN.yes; + const tag_id = try fr.takeLeb128(u64); + const has_children = (try fr.takeByte()) == DW.CHILDREN.yes; while (true) { - const attr_id = try fbr.takeLeb128(u64); - const form_id = try fbr.takeLeb128(u64); + const attr_id = try fr.takeLeb128(u64); + const form_id = try fr.takeLeb128(u64); if (attr_id == 0 and form_id == 0) break; try attrs.append(.{ .id = attr_id, .form_id = form_id, .payload = switch (form_id) { - FORM.implicit_const => try fbr.takeLeb128(i64), + FORM.implicit_const => try fr.takeLeb128(i64), else => undefined, }, }); @@ -1369,20 +923,21 @@ fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table } fn parseDie( - fbr: *Reader, + fr: *Reader, attrs_buf: []Die.Attr, abbrev_table: *const Abbrev.Table, format: Format, endian: Endian, + addr_size_bytes: u8, ) ScanError!?Die { - const abbrev_code = try fbr.takeLeb128(u64); + const abbrev_code = try fr.takeLeb128(u64); if (abbrev_code == 0) return null; const table_entry = abbrev_table.get(abbrev_code) orelse return bad(); const attrs = attrs_buf[0..table_entry.attrs.len]; for (attrs, table_entry.attrs) |*result_attr, attr| result_attr.* = .{ .id = attr.id, - .value = try parseFormValue(fbr, attr.form_id, format, endian, attr.payload), + .value = try parseFormValue(fr, attr.form_id, format, endian, addr_size_bytes, attr.payload), }; return .{ .tag_id = table_entry.tag_id, @@ -1392,55 +947,50 @@ fn parseDie( } /// Ensures that addresses in the returned LineTable are monotonically increasing. -fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !CompileUnit.SrcLocCache { - const endian = d.endian; - const compile_unit_cwd = try compile_unit.die.getAttrString(d, AT.comp_dir, d.section(.debug_line_str), compile_unit.*); +fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, endian: Endian, compile_unit: *const CompileUnit) !CompileUnit.SrcLocCache { + const compile_unit_cwd = try compile_unit.die.getAttrString(d, endian, AT.comp_dir, d.section(.debug_line_str), compile_unit); const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list); - var fbr: Reader = .fixed(d.section(.debug_line).?); - fbr.seek = @intCast(line_info_offset); + var fr: Reader = .fixed(d.section(.debug_line).?); + fr.seek = @intCast(line_info_offset); - const unit_header = try readUnitHeader(&fbr, endian); + const unit_header = try readUnitHeader(&fr, endian); if (unit_header.unit_length == 0) return missing(); const next_offset = unit_header.header_length + unit_header.unit_length; - const version = try fbr.takeInt(u16, endian); + const version = try fr.takeInt(u16, endian); if (version < 2) return bad(); - const addr_size: u8, const seg_size: u8 = if (version >= 5) .{ - try fbr.takeByte(), - try fbr.takeByte(), + const addr_size_bytes: u8, const seg_size: u8 = if (version >= 5) .{ + try fr.takeByte(), + try fr.takeByte(), } else .{ - switch (unit_header.format) { - .@"32" => 4, - .@"64" => 8, - }, + compile_unit.addr_size_bytes, 0, }; - _ = addr_size; - _ = seg_size; + if (seg_size != 0) return bad(); // unsupported - const prologue_length = try readAddress(&fbr, unit_header.format, endian); - const prog_start_offset = fbr.seek + prologue_length; + const prologue_length = try readFormatSizedInt(&fr, unit_header.format, endian); + const prog_start_offset = fr.seek + prologue_length; - const minimum_instruction_length = try fbr.takeByte(); + const minimum_instruction_length = try fr.takeByte(); if (minimum_instruction_length == 0) return bad(); if (version >= 4) { - const maximum_operations_per_instruction = try fbr.takeByte(); + const maximum_operations_per_instruction = try fr.takeByte(); _ = maximum_operations_per_instruction; } - const default_is_stmt = (try fbr.takeByte()) != 0; - const line_base = try fbr.takeByteSigned(); + const default_is_stmt = (try fr.takeByte()) != 0; + const line_base = try fr.takeByteSigned(); - const line_range = try fbr.takeByte(); + const line_range = try fr.takeByte(); if (line_range == 0) return bad(); - const opcode_base = try fbr.takeByte(); + const opcode_base = try fr.takeByte(); - const standard_opcode_lengths = try fbr.take(opcode_base - 1); + const standard_opcode_lengths = try fr.take(opcode_base - 1); var directories: ArrayList(FileEntry) = .empty; defer directories.deinit(gpa); @@ -1451,17 +1001,17 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) ! try directories.append(gpa, .{ .path = compile_unit_cwd }); while (true) { - const dir = try fbr.takeSentinel(0); + const dir = try fr.takeSentinel(0); if (dir.len == 0) break; try directories.append(gpa, .{ .path = dir }); } while (true) { - const file_name = try fbr.takeSentinel(0); + const file_name = try fr.takeSentinel(0); if (file_name.len == 0) break; - const dir_index = try fbr.takeLeb128(u32); - const mtime = try fbr.takeLeb128(u64); - const size = try fbr.takeLeb128(u64); + const dir_index = try fr.takeLeb128(u32); + const mtime = try fr.takeLeb128(u64); + const size = try fr.takeLeb128(u64); try file_entries.append(gpa, .{ .path = file_name, .dir_index = dir_index, @@ -1476,21 +1026,21 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) ! }; { var dir_ent_fmt_buf: [10]FileEntFmt = undefined; - const directory_entry_format_count = try fbr.takeByte(); + const directory_entry_format_count = try fr.takeByte(); if (directory_entry_format_count > dir_ent_fmt_buf.len) return bad(); for (dir_ent_fmt_buf[0..directory_entry_format_count]) |*ent_fmt| { ent_fmt.* = .{ - .content_type_code = try fbr.takeLeb128(u8), - .form_code = try fbr.takeLeb128(u16), + .content_type_code = try fr.takeLeb128(u8), + .form_code = try fr.takeLeb128(u16), }; } - const directories_count = try fbr.takeLeb128(usize); + const directories_count = try fr.takeLeb128(usize); for (try directories.addManyAsSlice(gpa, directories_count)) |*e| { e.* = .{ .path = &.{} }; for (dir_ent_fmt_buf[0..directory_entry_format_count]) |ent_fmt| { - const form_value = try parseFormValue(&fbr, ent_fmt.form_code, unit_header.format, endian, null); + const form_value = try parseFormValue(&fr, ent_fmt.form_code, unit_header.format, endian, addr_size_bytes, null); switch (ent_fmt.content_type_code) { DW.LNCT.path => e.path = try form_value.getString(d.*), DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), @@ -1507,22 +1057,22 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) ! } var file_ent_fmt_buf: [10]FileEntFmt = undefined; - const file_name_entry_format_count = try fbr.takeByte(); + const file_name_entry_format_count = try fr.takeByte(); if (file_name_entry_format_count > file_ent_fmt_buf.len) return bad(); for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| { ent_fmt.* = .{ - .content_type_code = try fbr.takeLeb128(u16), - .form_code = try fbr.takeLeb128(u16), + .content_type_code = try fr.takeLeb128(u16), + .form_code = try fr.takeLeb128(u16), }; } - const file_names_count = try fbr.takeLeb128(usize); + const file_names_count = try fr.takeLeb128(usize); try file_entries.ensureUnusedCapacity(gpa, file_names_count); for (try file_entries.addManyAsSlice(gpa, file_names_count)) |*e| { e.* = .{ .path = &.{} }; for (file_ent_fmt_buf[0..file_name_entry_format_count]) |ent_fmt| { - const form_value = try parseFormValue(&fbr, ent_fmt.form_code, unit_header.format, endian, null); + const form_value = try parseFormValue(&fr, ent_fmt.form_code, unit_header.format, endian, addr_size_bytes, null); switch (ent_fmt.content_type_code) { DW.LNCT.path => e.path = try form_value.getString(d.*), DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), @@ -1542,17 +1092,17 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) ! var line_table: CompileUnit.SrcLocCache.LineTable = .{}; errdefer line_table.deinit(gpa); - fbr.seek = @intCast(prog_start_offset); + fr.seek = @intCast(prog_start_offset); const next_unit_pos = line_info_offset + next_offset; - while (fbr.seek < next_unit_pos) { - const opcode = try fbr.takeByte(); + while (fr.seek < next_unit_pos) { + const opcode = try fr.takeByte(); if (opcode == DW.LNS.extended_op) { - const op_size = try fbr.takeLeb128(u64); + const op_size = try fr.takeLeb128(u64); if (op_size < 1) return bad(); - const sub_op = try fbr.takeByte(); + const sub_op = try fr.takeByte(); switch (sub_op) { DW.LNE.end_sequence => { // The row being added here is an "end" address, meaning @@ -1571,14 +1121,13 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) ! prog.reset(); }, DW.LNE.set_address => { - const addr = try fbr.takeInt(usize, endian); - prog.address = addr; + prog.address = try readAddress(&fr, endian, addr_size_bytes); }, DW.LNE.define_file => { - const path = try fbr.takeSentinel(0); - const dir_index = try fbr.takeLeb128(u32); - const mtime = try fbr.takeLeb128(u64); - const size = try fbr.takeLeb128(u64); + const path = try fr.takeSentinel(0); + const dir_index = try fr.takeLeb128(u32); + const mtime = try fr.takeLeb128(u64); + const size = try fr.takeLeb128(u64); try file_entries.append(gpa, .{ .path = path, .dir_index = dir_index, @@ -1586,7 +1135,7 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) ! .size = size, }); }, - else => try fbr.discardAll64(op_size - 1), + else => try fr.discardAll64(op_size - 1), } } else if (opcode >= opcode_base) { // special opcodes @@ -1604,19 +1153,19 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) ! prog.basic_block = false; }, DW.LNS.advance_pc => { - const arg = try fbr.takeLeb128(usize); + const arg = try fr.takeLeb128(u64); prog.address += arg * minimum_instruction_length; }, DW.LNS.advance_line => { - const arg = try fbr.takeLeb128(i64); + const arg = try fr.takeLeb128(i64); prog.line += arg; }, DW.LNS.set_file => { - const arg = try fbr.takeLeb128(usize); + const arg = try fr.takeLeb128(usize); prog.file = arg; }, DW.LNS.set_column => { - const arg = try fbr.takeLeb128(u64); + const arg = try fr.takeLeb128(u64); prog.column = arg; }, DW.LNS.negate_stmt => { @@ -1630,13 +1179,13 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) ! prog.address += inc_addr; }, DW.LNS.fixed_advance_pc => { - const arg = try fbr.takeInt(u16, endian); + const arg = try fr.takeInt(u16, endian); prog.address += arg; }, DW.LNS.set_prologue_end => {}, else => { if (opcode - 1 >= standard_opcode_lengths.len) return bad(); - try fbr.discardAll(standard_opcode_lengths[opcode - 1]); + try fr.discardAll(standard_opcode_lengths[opcode - 1]); }, } } @@ -1661,18 +1210,19 @@ fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) ! }; } -pub fn populateSrcLocCache(d: *Dwarf, gpa: Allocator, cu: *CompileUnit) ScanError!void { +pub fn populateSrcLocCache(d: *Dwarf, gpa: Allocator, endian: Endian, cu: *CompileUnit) ScanError!void { if (cu.src_loc_cache != null) return; - cu.src_loc_cache = try runLineNumberProgram(d, gpa, cu); + cu.src_loc_cache = try d.runLineNumberProgram(gpa, endian, cu); } pub fn getLineNumberInfo( d: *Dwarf, gpa: Allocator, + endian: Endian, compile_unit: *CompileUnit, target_address: u64, ) !std.debug.SourceLocation { - try populateSrcLocCache(d, gpa, compile_unit); + try d.populateSrcLocCache(gpa, endian, compile_unit); const slc = &compile_unit.src_loc_cache.?; const entry = try slc.findSource(target_address); const file_index = entry.file - @intFromBool(slc.version < 5); @@ -1696,7 +1246,7 @@ fn getLineString(di: Dwarf, offset: u64) ![:0]const u8 { return getStringGeneric(di.section(.debug_line_str), offset); } -fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 { +fn readDebugAddr(di: Dwarf, endian: Endian, compile_unit: *const CompileUnit, index: u64) !u64 { const debug_addr = di.section(.debug_addr) orelse return bad(); // addr_base points to the first item after the header, however we @@ -1705,139 +1255,40 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 { // The header is 8 or 12 bytes depending on is_64. if (compile_unit.addr_base < 8) return bad(); - const version = mem.readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], di.endian); + const version = mem.readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], endian); if (version != 5) return bad(); const addr_size = debug_addr[compile_unit.addr_base - 2]; const seg_size = debug_addr[compile_unit.addr_base - 1]; - const byte_offset = @as(usize, @intCast(compile_unit.addr_base + (addr_size + seg_size) * index)); + const byte_offset = compile_unit.addr_base + (addr_size + seg_size) * index; if (byte_offset + addr_size > debug_addr.len) return bad(); return switch (addr_size) { - 1 => debug_addr[byte_offset], - 2 => mem.readInt(u16, debug_addr[byte_offset..][0..2], di.endian), - 4 => mem.readInt(u32, debug_addr[byte_offset..][0..4], di.endian), - 8 => mem.readInt(u64, debug_addr[byte_offset..][0..8], di.endian), + 1 => debug_addr[@intCast(byte_offset)], + 2 => mem.readInt(u16, debug_addr[@intCast(byte_offset)..][0..2], endian), + 4 => mem.readInt(u32, debug_addr[@intCast(byte_offset)..][0..4], endian), + 8 => mem.readInt(u64, debug_addr[@intCast(byte_offset)..][0..8], endian), else => bad(), }; } -/// If `.eh_frame_hdr` is present, then only the header needs to be parsed. Otherwise, `.eh_frame` -/// and `.debug_frame` are scanned and a sorted list of FDEs is built for binary searching during -/// unwinding. Even if `.eh_frame_hdr` is used, we may find during unwinding that it's incomplete, -/// in which case we build the sorted list of FDEs at that point. -/// -/// See also `scanCieFdeInfo`. -pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void { - const endian = di.endian; - - if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: { - var fbr: Reader = .fixed(eh_frame_hdr); - - const version = try fbr.takeByte(); - if (version != 1) break :blk; - - const eh_frame_ptr_enc = try fbr.takeByte(); - if (eh_frame_ptr_enc == EH.PE.omit) break :blk; - const fde_count_enc = try fbr.takeByte(); - if (fde_count_enc == EH.PE.omit) break :blk; - const table_enc = try fbr.takeByte(); - if (table_enc == EH.PE.omit) break :blk; - - const eh_frame_ptr = cast(usize, try readEhPointer(&fbr, eh_frame_ptr_enc, @sizeOf(usize), .{ - .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.seek]), - .follow_indirect = true, - }, endian) orelse return bad()) orelse return bad(); - - const fde_count = cast(usize, try readEhPointer(&fbr, fde_count_enc, @sizeOf(usize), .{ - .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.seek]), - .follow_indirect = true, - }, endian) orelse return bad()) orelse return bad(); - - const entry_size = try ExceptionFrameHeader.entrySize(table_enc); - const entries_len = fde_count * entry_size; - if (entries_len > eh_frame_hdr.len - fbr.seek) return bad(); - - di.eh_frame_hdr = .{ - .eh_frame_ptr = eh_frame_ptr, - .table_enc = table_enc, - .fde_count = fde_count, - .entries = eh_frame_hdr[fbr.seek..][0..entries_len], - }; - - // No need to scan .eh_frame, we have a binary search table already - return; - } - - try di.scanCieFdeInfo(allocator, base_address); -} - -/// Scan `.eh_frame` and `.debug_frame` and build a sorted list of FDEs for binary searching during -/// unwinding. -pub fn scanCieFdeInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void { - const endian = di.endian; - const frame_sections = [2]Section.Id{ .eh_frame, .debug_frame }; - for (frame_sections) |frame_section| { - if (di.section(frame_section)) |section_data| { - var fbr: Reader = .fixed(section_data); - while (fbr.seek < fbr.buffer.len) { - const entry_header = try EntryHeader.read(&fbr, frame_section, endian); - switch (entry_header.type) { - .cie => { - const cie = try CommonInformationEntry.parse( - entry_header.entry_bytes, - di.sectionVirtualOffset(frame_section, base_address).?, - true, - entry_header.format, - frame_section, - entry_header.length_offset, - @sizeOf(usize), - di.endian, - ); - try di.cie_map.put(allocator, entry_header.length_offset, cie); - }, - .fde => |cie_offset| { - const cie = di.cie_map.get(cie_offset) orelse return bad(); - const fde = try FrameDescriptionEntry.parse( - entry_header.entry_bytes, - di.sectionVirtualOffset(frame_section, base_address).?, - true, - cie, - @sizeOf(usize), - di.endian, - ); - try di.fde_list.append(allocator, fde); - }, - .terminator => break, - } - } - - std.mem.sortUnstable(FrameDescriptionEntry, di.fde_list.items, {}, struct { - fn lessThan(ctx: void, a: FrameDescriptionEntry, b: FrameDescriptionEntry) bool { - _ = ctx; - return a.pc_begin < b.pc_begin; - } - }.lessThan); - } - } -} - fn parseFormValue( r: *Reader, form_id: u64, format: Format, endian: Endian, + addr_size_bytes: u8, implicit_const: ?i64, ) ScanError!FormValue { return switch (form_id) { // DWARF5.pdf page 213: the size of this value is encoded in the // compilation unit header as address size. - FORM.addr => .{ .addr = try readAddress(r, nativeFormat(), endian) }, + FORM.addr => .{ .addr = try readAddress(r, endian, addr_size_bytes) }, FORM.addrx1 => .{ .addrx = try r.takeByte() }, FORM.addrx2 => .{ .addrx = try r.takeInt(u16, endian) }, FORM.addrx3 => .{ .addrx = try r.takeInt(u24, endian) }, FORM.addrx4 => .{ .addrx = try r.takeInt(u32, endian) }, - FORM.addrx => .{ .addrx = try r.takeLeb128(usize) }, + FORM.addrx => .{ .addrx = try r.takeLeb128(u64) }, FORM.block1 => .{ .block = try r.take(try r.takeByte()) }, FORM.block2 => .{ .block = try r.take(try r.takeInt(u16, endian)) }, @@ -1854,7 +1305,7 @@ fn parseFormValue( FORM.exprloc => .{ .exprloc = try r.take(try r.takeLeb128(usize)) }, FORM.flag => .{ .flag = (try r.takeByte()) != 0 }, FORM.flag_present => .{ .flag = true }, - FORM.sec_offset => .{ .sec_offset = try readAddress(r, format, endian) }, + FORM.sec_offset => .{ .sec_offset = try readFormatSizedInt(r, format, endian) }, FORM.ref1 => .{ .ref = try r.takeByte() }, FORM.ref2 => .{ .ref = try r.takeInt(u16, endian) }, @@ -1862,18 +1313,18 @@ fn parseFormValue( FORM.ref8 => .{ .ref = try r.takeInt(u64, endian) }, FORM.ref_udata => .{ .ref = try r.takeLeb128(u64) }, - FORM.ref_addr => .{ .ref_addr = try readAddress(r, format, endian) }, + FORM.ref_addr => .{ .ref_addr = try readFormatSizedInt(r, format, endian) }, FORM.ref_sig8 => .{ .ref = try r.takeInt(u64, endian) }, FORM.string => .{ .string = try r.takeSentinel(0) }, - FORM.strp => .{ .strp = try readAddress(r, format, endian) }, + FORM.strp => .{ .strp = try readFormatSizedInt(r, format, endian) }, FORM.strx1 => .{ .strx = try r.takeByte() }, FORM.strx2 => .{ .strx = try r.takeInt(u16, endian) }, FORM.strx3 => .{ .strx = try r.takeInt(u24, endian) }, FORM.strx4 => .{ .strx = try r.takeInt(u32, endian) }, FORM.strx => .{ .strx = try r.takeLeb128(usize) }, - FORM.line_strp => .{ .line_strp = try readAddress(r, format, endian) }, - FORM.indirect => parseFormValue(r, try r.takeLeb128(u64), format, endian, implicit_const), + FORM.line_strp => .{ .line_strp = try readFormatSizedInt(r, format, endian) }, + FORM.indirect => parseFormValue(r, try r.takeLeb128(u64), format, endian, addr_size_bytes, implicit_const), FORM.implicit_const => .{ .sdata = implicit_const orelse return bad() }, FORM.loclistx => .{ .loclistx = try r.takeLeb128(u64) }, FORM.rnglistx => .{ .rnglistx = try r.takeLeb128(u64) }, @@ -1946,7 +1397,7 @@ const UnitHeader = struct { unit_length: u64, }; -fn readUnitHeader(r: *Reader, endian: Endian) ScanError!UnitHeader { +pub fn readUnitHeader(r: *Reader, endian: Endian) ScanError!UnitHeader { return switch (try r.takeInt(u32, endian)) { 0...0xfffffff0 - 1 => |unit_length| .{ .format = .@"32", @@ -1963,7 +1414,7 @@ fn readUnitHeader(r: *Reader, endian: Endian) ScanError!UnitHeader { } /// Returns the DWARF register number for an x86_64 register number found in compact unwind info -pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 { +pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u16 { return switch (unwind_reg_number) { 1 => 3, // RBX 2 => 12, // R12 @@ -1971,7 +1422,75 @@ pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 { 4 => 14, // R14 5 => 15, // R15 6 => 6, // RBP - else => error.InvalidUnwindRegisterNumber, + else => error.InvalidRegister, + }; +} + +/// Returns `null` for CPU architectures without an instruction pointer register. +pub fn ipRegNum(arch: std.Target.Cpu.Arch) ?u16 { + return switch (arch) { + .aarch64, .aarch64_be => 32, + .arm, .armeb, .thumb, .thumbeb => 15, + .hexagon => 76, + .loongarch32, .loongarch64 => 64, + .mips, .mipsel, .mips64, .mips64el => 66, + .powerpc, .powerpcle, .powerpc64, .powerpc64le => 67, + .riscv32, .riscv32be, .riscv64, .riscv64be => 65, + .s390x => 65, + .x86 => 8, + .x86_64 => 16, + else => null, + }; +} + +pub fn fpRegNum(arch: std.Target.Cpu.Arch) u16 { + return switch (arch) { + .aarch64, .aarch64_be => 29, + .arm, .armeb, .thumb, .thumbeb => 11, + .hexagon => 30, + .loongarch32, .loongarch64 => 22, + .mips, .mipsel, .mips64, .mips64el => 30, + .powerpc, .powerpcle, .powerpc64, .powerpc64le => 1, + .riscv32, .riscv32be, .riscv64, .riscv64be => 8, + .s390x => 11, + .x86 => 5, + .x86_64 => 6, + else => unreachable, + }; +} + +pub fn spRegNum(arch: std.Target.Cpu.Arch) u16 { + return switch (arch) { + .aarch64, .aarch64_be => 31, + .arm, .armeb, .thumb, .thumbeb => 13, + .hexagon => 29, + .loongarch32, .loongarch64 => 3, + .mips, .mipsel, .mips64, .mips64el => 29, + .powerpc, .powerpcle, .powerpc64, .powerpc64le => 1, + .riscv32, .riscv32be, .riscv64, .riscv64be => 2, + .s390x => 15, + .x86 => 4, + .x86_64 => 7, + else => unreachable, + }; +} + +/// Tells whether unwinding for this target is supported by the Dwarf standard. +/// +/// See also `std.debug.SelfInfo.can_unwind` which tells whether the Zig standard +/// library has a working implementation of unwinding for the current target. +pub fn supportsUnwinding(target: *const std.Target) bool { + return switch (target.cpu.arch) { + .amdgcn, + .nvptx, + .nvptx64, + .spirv32, + .spirv64, + => false, + + // Conservative guess. Feel free to update this logic with any targets + // that are known to not support Dwarf unwinding. + else => true, }; } @@ -1982,11 +1501,11 @@ pub fn bad() error{InvalidDebugInfo} { return error.InvalidDebugInfo; } -fn invalidDebugInfoDetected() void { +pub fn invalidDebugInfoDetected() void { if (debug_debug_mode) @panic("bad dwarf"); } -fn missing() error{MissingDebugInfo} { +pub fn missing() error{MissingDebugInfo} { if (debug_debug_mode) @panic("missing dwarf"); return error.MissingDebugInfo; } @@ -2000,460 +1519,41 @@ fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 { return str[casted_offset..last :0]; } -const EhPointerContext = struct { - // The address of the pointer field itself - pc_rel_base: u64, - - // Whether or not to follow indirect pointers. This should only be - // used when decoding pointers at runtime using the current process's - // debug info - follow_indirect: bool, - - // These relative addressing modes are only used in specific cases, and - // might not be available / required in all parsing contexts - data_rel_base: ?u64 = null, - text_rel_base: ?u64 = null, - function_rel_base: ?u64 = null, -}; - -fn readEhPointer(fbr: *Reader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext, endian: Endian) !?u64 { - if (enc == EH.PE.omit) return null; - - const value: union(enum) { - signed: i64, - unsigned: u64, - } = switch (enc & EH.PE.type_mask) { - EH.PE.absptr => .{ - .unsigned = switch (addr_size_bytes) { - 2 => try fbr.takeInt(u16, endian), - 4 => try fbr.takeInt(u32, endian), - 8 => try fbr.takeInt(u64, endian), - else => return error.InvalidAddrSize, - }, - }, - EH.PE.uleb128 => .{ .unsigned = try fbr.takeLeb128(u64) }, - EH.PE.udata2 => .{ .unsigned = try fbr.takeInt(u16, endian) }, - EH.PE.udata4 => .{ .unsigned = try fbr.takeInt(u32, endian) }, - EH.PE.udata8 => .{ .unsigned = try fbr.takeInt(u64, endian) }, - EH.PE.sleb128 => .{ .signed = try fbr.takeLeb128(i64) }, - EH.PE.sdata2 => .{ .signed = try fbr.takeInt(i16, endian) }, - EH.PE.sdata4 => .{ .signed = try fbr.takeInt(i32, endian) }, - EH.PE.sdata8 => .{ .signed = try fbr.takeInt(i64, endian) }, - else => return bad(), - }; - - const base = switch (enc & EH.PE.rel_mask) { - EH.PE.pcrel => ctx.pc_rel_base, - EH.PE.textrel => ctx.text_rel_base orelse return error.PointerBaseNotSpecified, - EH.PE.datarel => ctx.data_rel_base orelse return error.PointerBaseNotSpecified, - EH.PE.funcrel => ctx.function_rel_base orelse return error.PointerBaseNotSpecified, - else => null, - }; - - const ptr: u64 = if (base) |b| switch (value) { - .signed => |s| @intCast(try std.math.add(i64, s, @as(i64, @intCast(b)))), - // absptr can actually contain signed values in some cases (aarch64 MachO) - .unsigned => |u| u +% b, - } else switch (value) { - .signed => |s| @as(u64, @intCast(s)), - .unsigned => |u| u, +pub fn getSymbol(di: *Dwarf, gpa: Allocator, endian: Endian, address: u64) !std.debug.Symbol { + const compile_unit = di.findCompileUnit(endian, address) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => return .unknown, + else => return err, }; - - if ((enc & EH.PE.indirect) > 0 and ctx.follow_indirect) { - if (@sizeOf(usize) != addr_size_bytes) { - // See the documentation for `follow_indirect` - return error.NonNativeIndirection; - } - - const native_ptr = cast(usize, ptr) orelse return error.PointerOverflow; - return switch (addr_size_bytes) { - 2, 4, 8 => return @as(*const usize, @ptrFromInt(native_ptr)).*, - else => return error.UnsupportedAddrSize, - }; - } else { - return ptr; - } -} - -fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize { - if (pc_rel_offset < 0) { - return std.math.sub(usize, field_ptr, @as(usize, @intCast(-pc_rel_offset))); - } else { - return std.math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset))); - } -} - -pub const ElfModule = struct { - base_address: usize, - dwarf: Dwarf, - mapped_memory: []align(std.heap.page_size_min) const u8, - external_mapped_memory: ?[]align(std.heap.page_size_min) const u8, - - pub fn deinit(self: *@This(), allocator: Allocator) void { - self.dwarf.deinit(allocator); - std.posix.munmap(self.mapped_memory); - if (self.external_mapped_memory) |m| std.posix.munmap(m); - } - - pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol { - // Translate the VA into an address into this object - const relocated_address = address - self.base_address; - return self.dwarf.getSymbol(allocator, relocated_address); - } - - pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf { - _ = allocator; - _ = address; - return &self.dwarf; - } - - pub const LoadError = error{ - InvalidDebugInfo, - MissingDebugInfo, - InvalidElfMagic, - InvalidElfVersion, - InvalidElfEndian, - /// TODO: implement this and then remove this error code - UnimplementedDwarfForeignEndian, - /// The debug info may be valid but this implementation uses memory - /// mapping which limits things to usize. If the target debug info is - /// 64-bit and host is 32-bit, there may be debug info that is not - /// supportable using this method. - Overflow, - - PermissionDenied, - LockedMemoryLimitExceeded, - MemoryMappingNotSupported, - } || Allocator.Error || std.fs.File.OpenError || OpenError; - - /// Reads debug info from an already mapped ELF file. - /// - /// If the required sections aren't present but a reference to external debug - /// info is, then this this function will recurse to attempt to load the debug - /// sections from an external file. - pub fn load( - gpa: Allocator, - mapped_mem: []align(std.heap.page_size_min) const u8, - build_id: ?[]const u8, - expected_crc: ?u32, - parent_sections: *Dwarf.SectionArray, - parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8, - elf_filename: ?[]const u8, - ) LoadError!Dwarf.ElfModule { - if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo; - - const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]); - if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; - if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; - - const endian: Endian = switch (hdr.e_ident[elf.EI_DATA]) { - elf.ELFDATA2LSB => .little, - elf.ELFDATA2MSB => .big, - else => return error.InvalidElfEndian, - }; - if (endian != native_endian) return error.UnimplementedDwarfForeignEndian; - - const shoff = hdr.e_shoff; - const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx); - const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[cast(usize, str_section_off) orelse return error.Overflow])); - const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size]; - const shdrs = @as( - [*]const elf.Shdr, - @ptrCast(@alignCast(&mapped_mem[shoff])), - )[0..hdr.e_shnum]; - - var sections: Dwarf.SectionArray = Dwarf.null_section_array; - - // Combine section list. This takes ownership over any owned sections from the parent scope. - for (parent_sections, §ions) |*parent, *section_elem| { - if (parent.*) |*p| { - section_elem.* = p.*; - p.owned = false; - } - } - errdefer for (sections) |opt_section| if (opt_section) |s| if (s.owned) gpa.free(s.data); - - var separate_debug_filename: ?[]const u8 = null; - var separate_debug_crc: ?u32 = null; - - for (shdrs) |*shdr| { - if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue; - const name = mem.sliceTo(header_strings[shdr.sh_name..], 0); - - if (mem.eql(u8, name, ".gnu_debuglink")) { - const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); - const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0); - const crc_offset = mem.alignForward(usize, debug_filename.len + 1, 4); - const crc_bytes = gnu_debuglink[crc_offset..][0..4]; - separate_debug_crc = mem.readInt(u32, crc_bytes, endian); - separate_debug_filename = debug_filename; - continue; - } - - var section_index: ?usize = null; - inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |sect, i| { - if (mem.eql(u8, "." ++ sect.name, name)) section_index = i; - } - if (section_index == null) continue; - if (sections[section_index.?] != null) continue; - - const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); - sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: { - var section_reader: Reader = .fixed(section_bytes); - const chdr = section_reader.takeStruct(elf.Chdr, endian) catch continue; - if (chdr.ch_type != .ZLIB) continue; - - var decompress: std.compress.flate.Decompress = .init(§ion_reader, .zlib, &.{}); - var decompressed_section: ArrayList(u8) = .empty; - defer decompressed_section.deinit(gpa); - decompress.reader.appendRemainingUnlimited(gpa, &decompressed_section) catch { - invalidDebugInfoDetected(); - continue; - }; - if (chdr.ch_size != decompressed_section.items.len) { - invalidDebugInfoDetected(); - continue; - } - break :blk .{ - .data = try decompressed_section.toOwnedSlice(gpa), - .virtual_address = shdr.sh_addr, - .owned = true, - }; - } else .{ - .data = section_bytes, - .virtual_address = shdr.sh_addr, - .owned = false, - }; - } - - const missing_debug_info = - sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or - sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or - sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or - sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; - - // Attempt to load debug info from an external file - // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html - if (missing_debug_info) { - - // Only allow one level of debug info nesting - if (parent_mapped_mem) |_| { - return error.MissingDebugInfo; - } - - // $XDG_CACHE_HOME/debuginfod_client/<buildid>/debuginfo - // This only opportunisticly tries to load from the debuginfod cache, but doesn't try to populate it. - // One can manually run `debuginfod-find debuginfo PATH` to download the symbols - if (build_id) |id| blk: { - var debuginfod_dir: std.fs.Dir = switch (builtin.os.tag) { - .wasi, .windows => break :blk, - else => dir: { - if (std.posix.getenv("DEBUGINFOD_CACHE_PATH")) |path| { - break :dir std.fs.openDirAbsolute(path, .{}) catch break :blk; - } - if (std.posix.getenv("XDG_CACHE_HOME")) |cache_path| { - if (cache_path.len > 0) { - const path = std.fs.path.join(gpa, &[_][]const u8{ cache_path, "debuginfod_client" }) catch break :blk; - defer gpa.free(path); - break :dir std.fs.openDirAbsolute(path, .{}) catch break :blk; - } - } - if (std.posix.getenv("HOME")) |home_path| { - const path = std.fs.path.join(gpa, &[_][]const u8{ home_path, ".cache", "debuginfod_client" }) catch break :blk; - defer gpa.free(path); - break :dir std.fs.openDirAbsolute(path, .{}) catch break :blk; - } - break :blk; - }, - }; - defer debuginfod_dir.close(); - - const filename = std.fmt.allocPrint(gpa, "{x}/debuginfo", .{id}) catch break :blk; - defer gpa.free(filename); - - const path: Path = .{ - .root_dir = .{ .path = null, .handle = debuginfod_dir }, - .sub_path = filename, - }; - - return loadPath(gpa, path, null, separate_debug_crc, §ions, mapped_mem) catch break :blk; - } - - const global_debug_directories = [_][]const u8{ - "/usr/lib/debug", - }; - - // <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug - if (build_id) |id| blk: { - if (id.len < 3) break :blk; - - // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice - const extension = ".debug"; - var id_prefix_buf: [2]u8 = undefined; - var filename_buf: [38 + extension.len]u8 = undefined; - - _ = std.fmt.bufPrint(&id_prefix_buf, "{x}", .{id[0..1]}) catch unreachable; - const filename = std.fmt.bufPrint(&filename_buf, "{x}" ++ extension, .{id[1..]}) catch break :blk; - - for (global_debug_directories) |global_directory| { - const path: Path = .{ - .root_dir = std.Build.Cache.Directory.cwd(), - .sub_path = try std.fs.path.join(gpa, &.{ - global_directory, ".build-id", &id_prefix_buf, filename, - }), - }; - defer gpa.free(path.sub_path); - - return loadPath(gpa, path, null, separate_debug_crc, §ions, mapped_mem) catch continue; - } - } - - // use the path from .gnu_debuglink, in the same search order as gdb - if (separate_debug_filename) |separate_filename| blk: { - if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) - return error.MissingDebugInfo; - - exe_dir: { - var exe_dir_buf: [std.fs.max_path_bytes]u8 = undefined; - const exe_dir_path = std.fs.selfExeDirPath(&exe_dir_buf) catch break :exe_dir; - var exe_dir = std.fs.openDirAbsolute(exe_dir_path, .{}) catch break :exe_dir; - defer exe_dir.close(); - - // <exe_dir>/<gnu_debuglink> - if (loadPath( - gpa, - .{ - .root_dir = .{ .path = null, .handle = exe_dir }, - .sub_path = separate_filename, - }, - null, - separate_debug_crc, - §ions, - mapped_mem, - )) |debug_info| { - return debug_info; - } else |_| {} - - // <exe_dir>/.debug/<gnu_debuglink> - const path: Path = .{ - .root_dir = .{ .path = null, .handle = exe_dir }, - .sub_path = try std.fs.path.join(gpa, &.{ ".debug", separate_filename }), - }; - defer gpa.free(path.sub_path); - - if (loadPath(gpa, path, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {} - } - - var cwd_buf: [std.fs.max_path_bytes]u8 = undefined; - const cwd_path = std.posix.realpath(".", &cwd_buf) catch break :blk; - - // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink> - for (global_debug_directories) |global_directory| { - const path: Path = .{ - .root_dir = std.Build.Cache.Directory.cwd(), - .sub_path = try std.fs.path.join(gpa, &.{ global_directory, cwd_path, separate_filename }), - }; - defer gpa.free(path.sub_path); - if (loadPath(gpa, path, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {} - } - } - - return error.MissingDebugInfo; - } - - var di: Dwarf = .{ - .endian = endian, - .sections = sections, - .is_macho = false, - }; - - try Dwarf.open(&di, gpa); - - return .{ - .base_address = 0, - .dwarf = di, - .mapped_memory = parent_mapped_mem orelse mapped_mem, - .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null, - }; - } - - pub fn loadPath( - gpa: Allocator, - elf_file_path: Path, - build_id: ?[]const u8, - expected_crc: ?u32, - parent_sections: *Dwarf.SectionArray, - parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8, - ) LoadError!Dwarf.ElfModule { - const elf_file = elf_file_path.root_dir.handle.openFile(elf_file_path.sub_path, .{}) catch |err| switch (err) { - error.FileNotFound => return missing(), + return .{ + .name = di.getSymbolName(address), + .compile_unit_name = compile_unit.die.getAttrString(di, endian, std.dwarf.AT.name, di.section(.debug_str), compile_unit) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => null, + }, + .source_location = di.getLineNumberInfo(gpa, endian, compile_unit, address) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => null, else => return err, - }; - defer elf_file.close(); - - const end_pos = elf_file.getEndPos() catch return bad(); - const file_len = cast(usize, end_pos) orelse return error.Overflow; - - const mapped_mem = std.posix.mmap( - null, - file_len, - std.posix.PROT.READ, - .{ .TYPE = .SHARED }, - elf_file.handle, - 0, - ) catch |err| switch (err) { - error.MappingAlreadyExists => unreachable, - else => |e| return e, - }; - errdefer std.posix.munmap(mapped_mem); - - return load( - gpa, - mapped_mem, - build_id, - expected_crc, - parent_sections, - parent_mapped_mem, - elf_file_path.sub_path, - ); - } -}; - -pub fn getSymbol(di: *Dwarf, allocator: Allocator, address: u64) !std.debug.Symbol { - if (di.findCompileUnit(address)) |compile_unit| { - return .{ - .name = di.getSymbolName(address) orelse "???", - .compile_unit_name = compile_unit.die.getAttrString(di, std.dwarf.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => "???", - }, - .source_location = di.getLineNumberInfo(allocator, compile_unit, address) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => null, - else => return err, - }, - }; - } else |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => return .{}, - else => return err, - } -} - -pub fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 { - const start = cast(usize, offset) orelse return error.Overflow; - const end = start + (cast(usize, size) orelse return error.Overflow); - return ptr[start..end]; + }, + }; } -fn readAddress(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 { +/// DWARF5 7.4: "In the 32-bit DWARF format, all values that represent lengths of DWARF sections and +/// offsets relative to the beginning of DWARF sections are represented using four bytes. In the +/// 64-bit DWARF format, all values that represent lengths of DWARF sections and offsets relative to +/// the beginning of DWARF sections are represented using eight bytes". +/// +/// This function is for reading such values. +fn readFormatSizedInt(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 { return switch (format) { .@"32" => try r.takeInt(u32, endian), .@"64" => try r.takeInt(u64, endian), }; } -fn nativeFormat() std.dwarf.Format { - return switch (@sizeOf(usize)) { - 4 => .@"32", - 8 => .@"64", - else => @compileError("unsupported @sizeOf(usize)"), +fn readAddress(r: *Reader, endian: Endian, addr_size_bytes: u8) !u64 { + return switch (addr_size_bytes) { + 2 => try r.takeInt(u16, endian), + 4 => try r.takeInt(u32, endian), + 8 => try r.takeInt(u64, endian), + else => return bad(), }; } diff --git a/lib/std/debug/Dwarf/SelfUnwinder.zig b/lib/std/debug/Dwarf/SelfUnwinder.zig new file mode 100644 index 0000000000..3339a464d4 --- /dev/null +++ b/lib/std/debug/Dwarf/SelfUnwinder.zig @@ -0,0 +1,347 @@ +//! Implements stack unwinding based on `Dwarf.Unwind`. The caller is responsible for providing the +//! initialized `Dwarf.Unwind` from the `.debug_frame` (or equivalent) section; this type handles +//! computing and applying the CFI register rules to evolve a `std.debug.cpu_context.Native` through +//! stack frames, hence performing the virtual unwind. +//! +//! Notably, this type is a valid implementation of `std.debug.SelfInfo.UnwindContext`. + +/// The state of the CPU in the current stack frame. +cpu_state: std.debug.cpu_context.Native, +/// The value of the Program Counter in this frame. This is almost the same as the value of the IP +/// register in `cpu_state`, but may be off by one because the IP is typically a *return* address. +pc: usize, + +cfi_vm: Dwarf.Unwind.VirtualMachine, +expr_vm: Dwarf.expression.StackMachine(.{ .call_frame_context = true }), + +pub const CacheEntry = struct { + const max_rules = 32; + + pc: usize, + cie: *const Dwarf.Unwind.CommonInformationEntry, + cfa_rule: Dwarf.Unwind.VirtualMachine.CfaRule, + num_rules: u8, + rules_regs: [max_rules]u16, + rules: [max_rules]Dwarf.Unwind.VirtualMachine.RegisterRule, + + pub fn find(entries: []const CacheEntry, pc: usize) ?*const CacheEntry { + assert(pc != 0); + const idx = std.hash.int(pc) % entries.len; + const entry = &entries[idx]; + return if (entry.pc == pc) entry else null; + } + + pub fn populate(entry: *const CacheEntry, entries: []CacheEntry) void { + const idx = std.hash.int(entry.pc) % entries.len; + entries[idx] = entry.*; + } + + pub const empty: CacheEntry = .{ + .pc = 0, + .cie = undefined, + .cfa_rule = undefined, + .num_rules = undefined, + .rules_regs = undefined, + .rules = undefined, + }; +}; + +pub fn init(cpu_context: *const std.debug.cpu_context.Native) SelfUnwinder { + // `@constCast` is safe because we aren't going to store to the resulting pointer. + const raw_pc_ptr = regNative(@constCast(cpu_context), ip_reg_num) catch |err| switch (err) { + error.InvalidRegister => unreachable, // `ip_reg_num` is definitely valid + error.UnsupportedRegister => unreachable, // the implementation needs to support ip + error.IncompatibleRegisterSize => unreachable, // ip is definitely `usize`-sized + }; + const pc = stripInstructionPtrAuthCode(raw_pc_ptr.*); + return .{ + .cpu_state = cpu_context.*, + .pc = pc, + .cfi_vm = .{}, + .expr_vm = .{}, + }; +} + +pub fn deinit(unwinder: *SelfUnwinder, gpa: Allocator) void { + unwinder.cfi_vm.deinit(gpa); + unwinder.expr_vm.deinit(gpa); + unwinder.* = undefined; +} + +pub fn getFp(unwinder: *const SelfUnwinder) usize { + // `@constCast` is safe because we aren't going to store to the resulting pointer. + const ptr = regNative(@constCast(&unwinder.cpu_state), fp_reg_num) catch |err| switch (err) { + error.InvalidRegister => unreachable, // `fp_reg_num` is definitely valid + error.UnsupportedRegister => unreachable, // the implementation needs to support fp + error.IncompatibleRegisterSize => unreachable, // fp is a pointer so is `usize`-sized + }; + return ptr.*; +} + +/// Compute the rule set for the address `unwinder.pc` from the information in `unwind`. The caller +/// may store the returned rule set in a simple fixed-size cache keyed on the `pc` field to avoid +/// frequently recomputing register rules when unwinding many times. +/// +/// To actually apply the computed rules, see `next`. +pub fn computeRules( + unwinder: *SelfUnwinder, + gpa: Allocator, + unwind: *const Dwarf.Unwind, + load_offset: usize, + explicit_fde_offset: ?usize, +) !CacheEntry { + assert(unwinder.pc != 0); + + const pc_vaddr = unwinder.pc - load_offset; + + const fde_offset = explicit_fde_offset orelse try unwind.lookupPc( + pc_vaddr, + @sizeOf(usize), + native_endian, + ) orelse return error.MissingDebugInfo; + const cie, const fde = try unwind.getFde(fde_offset, native_endian); + + // `lookupPc` can return false positives, so check if the FDE *actually* includes the pc + if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) { + return error.MissingDebugInfo; + } + + unwinder.cfi_vm.reset(); + const row = try unwinder.cfi_vm.runTo(gpa, pc_vaddr, cie, &fde, @sizeOf(usize), native_endian); + + var entry: CacheEntry = .{ + .pc = unwinder.pc, + .cie = cie, + .cfa_rule = row.cfa, + .num_rules = undefined, + .rules_regs = undefined, + .rules = undefined, + }; + var i: usize = 0; + for (unwinder.cfi_vm.rowColumns(&row)) |col| { + if (i == CacheEntry.max_rules) return error.UnsupportedDebugInfo; + + _ = unwinder.cpu_state.dwarfRegisterBytes(col.register) catch |err| switch (err) { + // Reading an unsupported register during unwinding will result in an error, so there is + // no point wasting a rule slot in the cache entry for it. + error.UnsupportedRegister => continue, + error.InvalidRegister => return error.InvalidDebugInfo, + }; + entry.rules_regs[i] = col.register; + entry.rules[i] = col.rule; + i += 1; + } + entry.num_rules = @intCast(i); + return entry; +} + +/// Applies the register rules given in `cache_entry` to the current state of `unwinder`. The caller +/// is responsible for ensuring that `cache_entry` contains the correct rule set for `unwinder.pc`. +/// +/// `unwinder.cpu_state` and `unwinder.pc` are updated to refer to the next frame, and this frame's +/// return address is returned as a `usize`. +pub fn next(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheEntry) std.debug.SelfInfoError!usize { + return unwinder.nextInner(gpa, cache_entry) catch |err| switch (err) { + error.OutOfMemory, + error.InvalidDebugInfo, + => |e| return e, + + error.UnsupportedRegister, + error.UnimplementedExpressionCall, + error.UnimplementedOpcode, + error.UnimplementedUserOpcode, + error.UnimplementedTypedComparison, + error.UnimplementedTypeConversion, + error.UnknownExpressionOpcode, + => return error.UnsupportedDebugInfo, + + error.ReadFailed, + error.EndOfStream, + error.Overflow, + error.IncompatibleRegisterSize, + error.InvalidRegister, + error.IncompleteExpressionContext, + error.InvalidCFAOpcode, + error.InvalidExpression, + error.InvalidFrameBase, + error.InvalidIntegralTypeSize, + error.InvalidSubExpression, + error.InvalidTypeLength, + error.TruncatedIntegralType, + error.DivisionByZero, + => return error.InvalidDebugInfo, + }; +} + +fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheEntry) !usize { + const format = cache_entry.cie.format; + + const cfa = switch (cache_entry.cfa_rule) { + .none => return error.InvalidDebugInfo, + .reg_off => |ro| cfa: { + const ptr = try regNative(&unwinder.cpu_state, ro.register); + break :cfa try applyOffset(ptr.*, ro.offset); + }, + .expression => |expr| cfa: { + // On most implemented architectures, the CFA is defined to be the previous frame's SP. + // + // On s390x, it's defined to be SP + 160 (ELF ABI s390x Supplement §1.6.3); however, + // what this actually means is that there will be a `def_cfa r15 + 160`, so nothing + // special for us to do. + const prev_cfa_val = (try regNative(&unwinder.cpu_state, sp_reg_num)).*; + unwinder.expr_vm.reset(); + const value = try unwinder.expr_vm.run(expr, gpa, .{ + .format = format, + .cpu_context = &unwinder.cpu_state, + }, prev_cfa_val) orelse return error.InvalidDebugInfo; + switch (value) { + .generic => |g| break :cfa g, + else => return error.InvalidDebugInfo, + } + }, + }; + + // Create a copy of the CPU state, to which we will apply the new rules. + var new_cpu_state = unwinder.cpu_state; + + // On all implemented architectures, the CFA is defined to be the previous frame's SP + (try regNative(&new_cpu_state, sp_reg_num)).* = cfa; + + const return_address_register = cache_entry.cie.return_address_register; + var has_return_address = true; + + const rules_len = cache_entry.num_rules; + for (cache_entry.rules_regs[0..rules_len], cache_entry.rules[0..rules_len]) |register, rule| { + const new_val: union(enum) { + same, + undefined, + val: usize, + bytes: []const u8, + } = switch (rule) { + .default => val: { + // The way things are supposed to work is that `.undefined` is the default rule + // unless an ABI says otherwise (e.g. aarch64, s390x). + // + // Unfortunately, at some point, a decision was made to have libgcc's unwinder + // assume `.same` as the default for all registers. Compilers then started depending + // on this, and the practice was carried forward to LLVM's libunwind and some of its + // backends. + break :val .same; + }, + .undefined => .undefined, + .same_value => .same, + .offset => |offset| val: { + const ptr: *const usize = @ptrFromInt(try applyOffset(cfa, offset)); + break :val .{ .val = ptr.* }; + }, + .val_offset => |offset| .{ .val = try applyOffset(cfa, offset) }, + .register => |r| .{ .bytes = try unwinder.cpu_state.dwarfRegisterBytes(r) }, + .expression => |expr| val: { + unwinder.expr_vm.reset(); + const value = try unwinder.expr_vm.run(expr, gpa, .{ + .format = format, + .cpu_context = &unwinder.cpu_state, + }, cfa) orelse return error.InvalidDebugInfo; + const ptr: *const usize = switch (value) { + .generic => |addr| @ptrFromInt(addr), + else => return error.InvalidDebugInfo, + }; + break :val .{ .val = ptr.* }; + }, + .val_expression => |expr| val: { + unwinder.expr_vm.reset(); + const value = try unwinder.expr_vm.run(expr, gpa, .{ + .format = format, + .cpu_context = &unwinder.cpu_state, + }, cfa) orelse return error.InvalidDebugInfo; + switch (value) { + .generic => |val| break :val .{ .val = val }, + else => return error.InvalidDebugInfo, + } + }, + }; + switch (new_val) { + .same => {}, + .undefined => { + const dest = try new_cpu_state.dwarfRegisterBytes(@intCast(register)); + @memset(dest, undefined); + + // If the return address register is explicitly set to `.undefined`, it means that + // there are no more frames to unwind. + if (register == return_address_register) { + has_return_address = false; + } + }, + .val => |val| { + const dest = try new_cpu_state.dwarfRegisterBytes(@intCast(register)); + if (dest.len != @sizeOf(usize)) return error.InvalidDebugInfo; + const dest_ptr: *align(1) usize = @ptrCast(dest); + dest_ptr.* = val; + }, + .bytes => |src| { + const dest = try new_cpu_state.dwarfRegisterBytes(@intCast(register)); + if (dest.len != src.len) return error.InvalidDebugInfo; + @memcpy(dest, src); + }, + } + } + + const return_address = if (has_return_address) + stripInstructionPtrAuthCode((try regNative(&new_cpu_state, return_address_register)).*) + else + 0; + + (try regNative(&new_cpu_state, ip_reg_num)).* = return_address; + + // The new CPU state is complete; flush changes. + unwinder.cpu_state = new_cpu_state; + + // The caller will subtract 1 from the return address to get an address corresponding to the + // function call. However, if this is a signal frame, that's actually incorrect, because the + // "return address" we have is the instruction which triggered the signal (if the signal + // handler returned, the instruction would be re-run). Compensate for this by incrementing + // the address in that case. + const adjusted_ret_addr = if (cache_entry.cie.is_signal_frame) return_address +| 1 else return_address; + + // We also want to do that same subtraction here to get the PC for the next frame's FDE. + // This is because if the callee was noreturn, then the function call might be the caller's + // last instruction, so `return_address` might actually point outside of it! + unwinder.pc = adjusted_ret_addr -| 1; + + return adjusted_ret_addr; +} + +pub fn regNative(ctx: *std.debug.cpu_context.Native, num: u16) error{ + InvalidRegister, + UnsupportedRegister, + IncompatibleRegisterSize, +}!*align(1) usize { + const bytes = try ctx.dwarfRegisterBytes(num); + if (bytes.len != @sizeOf(usize)) return error.IncompatibleRegisterSize; + return @ptrCast(bytes); +} + +/// Since register rules are applied (usually) during a panic, +/// checked addition / subtraction is used so that we can return +/// an error and fall back to FP-based unwinding. +fn applyOffset(base: usize, offset: i64) !usize { + return if (offset >= 0) + try std.math.add(usize, base, @as(usize, @intCast(offset))) + else + try std.math.sub(usize, base, @as(usize, @intCast(-offset))); +} + +const ip_reg_num = Dwarf.ipRegNum(builtin.target.cpu.arch).?; +const fp_reg_num = Dwarf.fpRegNum(builtin.target.cpu.arch); +const sp_reg_num = Dwarf.spRegNum(builtin.target.cpu.arch); + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const Dwarf = std.debug.Dwarf; +const assert = std.debug.assert; +const stripInstructionPtrAuthCode = std.debug.stripInstructionPtrAuthCode; + +const builtin = @import("builtin"); +const native_endian = builtin.target.cpu.arch.endian(); + +const SelfUnwinder = @This(); diff --git a/lib/std/debug/Dwarf/Unwind.zig b/lib/std/debug/Dwarf/Unwind.zig new file mode 100644 index 0000000000..d351c0421e --- /dev/null +++ b/lib/std/debug/Dwarf/Unwind.zig @@ -0,0 +1,702 @@ +//! Contains state relevant to stack unwinding through the DWARF `.debug_frame` section, or the +//! `.eh_frame` section which is an extension of the former specified by Linux Standard Base Core. +//! Like `Dwarf`, no assumptions are made about the host's relationship to the target of the unwind +//! information -- unwind data for any target can be read by any host. +//! +//! `Unwind` specifically deals with loading the data from CIEs and FDEs in the section, and with +//! performing fast lookups of a program counter's corresponding FDE. The CFI instructions in the +//! CIEs and FDEs can be interpreted by `VirtualMachine`. +//! +//! The typical usage of `Unwind` is as follows: +//! +//! * Initialize with `initEhFrameHdr` or `initSection`, depending on the available data +//! * Call `prepare` to scan CIEs and, if necessary, construct a search table +//! * Call `lookupPc` to find the section offset of the FDE corresponding to a PC +//! * Call `getFde` to load the corresponding FDE and CIE +//! * Check that the PC does indeed fall in that range (`lookupPc` may return a false positive) +//! * Interpret the embedded CFI instructions using `VirtualMachine` +//! +//! In some cases, such as when using the "compact unwind" data in Mach-O binaries, the FDE offsets +//! may already be known. In that case, no call to `lookupPc` is necessary, which means the call to +//! `prepare` can be optimized to only scan CIEs. + +pub const VirtualMachine = @import("Unwind/VirtualMachine.zig"); + +frame_section: struct { + id: Section, + /// The virtual address of the start of the section. "Virtual address" refers to the address in + /// the binary (e.g. `sh_addr` in an ELF file); the equivalent runtime address may be relocated + /// in position-independent binaries. + vaddr: u64, + /// The full contents of the section. May have imprecise bounds depending on `section`. This + /// memory is externally managed. + /// + /// For `.debug_frame`, the slice length is exactly equal to the section length. This is needed + /// to know the number of CIEs and FDEs. + /// + /// For `.eh_frame`, the slice length may exceed the section length, i.e. the slice may refer to + /// more bytes than are in the second. This restriction exists because `.eh_frame_hdr` only + /// includes the address of the loaded `.eh_frame` data, not its length. It is not a problem + /// because unlike `.debug_frame`, the end of the CIE/FDE list is signaled through a sentinel + /// value. If this slice does have bounds, they will still be checked, preventing crashes when + /// reading potentially-invalid `.eh_frame` data from files. + bytes: []const u8, +}, + +/// A structure allowing fast lookups of the FDE corresponding to a particular PC. We use a binary +/// search table for the lookup; essentially, a list of all FDEs ordered by PC range. `null` means +/// the lookup data is not yet populated, so `prepare` must be called before `lookupPc`. +lookup: ?union(enum) { + /// The `.eh_frame_hdr` section contains a pre-computed search table which we can use. + eh_frame_hdr: struct { + /// Virtual address of the `.eh_frame_hdr` section. + vaddr: u64, + table: EhFrameHeader.SearchTable, + }, + /// There is no pre-computed search table, so we have built one ourselves. + /// Allocated into `gpa` and freed by `deinit`. + sorted_fdes: []SortedFdeEntry, +}, + +/// Initially empty; populated by `prepare`. +cie_list: std.MultiArrayList(struct { + offset: u64, + cie: CommonInformationEntry, +}), + +const SortedFdeEntry = struct { + /// This FDE's value of `pc_begin`. + pc_begin: u64, + /// Offset into the section of the corresponding FDE, including the entry header. + fde_offset: u64, +}; + +pub const Section = enum { debug_frame, eh_frame }; + +/// Initialize with unwind information from a header loaded from an `.eh_frame_hdr` section, and a +/// pointer to the contents of the `.eh_frame` section. +/// +/// `.eh_frame_hdr` may embed a binary search table of FDEs. If it does, we will use that table for +/// PC lookups rather than spending time constructing our own search table. +pub fn initEhFrameHdr(header: EhFrameHeader, section_vaddr: u64, section_bytes_ptr: [*]const u8) Unwind { + return .{ + .frame_section = .{ + .id = .eh_frame, + .bytes = maxSlice(section_bytes_ptr), + .vaddr = header.eh_frame_vaddr, + }, + .lookup = if (header.search_table) |table| .{ .eh_frame_hdr = .{ + .vaddr = section_vaddr, + .table = table, + } } else null, + .cie_list = .empty, + }; +} + +/// Initialize with unwind information from the contents of a `.debug_frame` or `.eh_frame` section. +/// +/// If the `.eh_frame_hdr` section is available, consider instead using `initEhFrameHdr`, which +/// allows the implementation to use a search table embedded in that section if it is available. +pub fn initSection(section: Section, section_vaddr: u64, section_bytes: []const u8) Unwind { + return .{ + .frame_section = .{ + .id = section, + .bytes = section_bytes, + .vaddr = section_vaddr, + }, + .lookup = null, + .cie_list = .empty, + }; +} + +pub fn deinit(unwind: *Unwind, gpa: Allocator) void { + if (unwind.lookup) |lookup| switch (lookup) { + .eh_frame_hdr => {}, + .sorted_fdes => |fdes| gpa.free(fdes), + }; + for (unwind.cie_list.items(.cie)) |*cie| { + if (cie.last_row) |*lr| { + gpa.free(lr.cols); + } + } + unwind.cie_list.deinit(gpa); +} + +/// Decoded version of the `.eh_frame_hdr` section. +pub const EhFrameHeader = struct { + /// The virtual address (i.e. as given in the binary, before relocations) of the `.eh_frame` + /// section. This value is important when using `.eh_frame_hdr` to find debug information for + /// the current binary, because it allows locating where the `.eh_frame` section is loaded in + /// memory (by adding it to the ELF module's base address). + eh_frame_vaddr: u64, + search_table: ?SearchTable, + + pub const SearchTable = struct { + /// The byte offset of the search table into the `.eh_frame_hdr` section. + offset: u8, + encoding: EH.PE, + fde_count: usize, + /// The actual table entries are viewed as a plain byte slice because `encoding` causes the + /// size of entries in the table to vary. + entries: []const u8, + + /// Returns the vaddr of the FDE for `pc`, or `null` if no matching FDE was found. + fn findEntry( + table: *const SearchTable, + eh_frame_hdr_vaddr: u64, + pc: u64, + addr_size_bytes: u8, + endian: Endian, + ) !?u64 { + const table_vaddr = eh_frame_hdr_vaddr + table.offset; + const entry_size = try entrySize(table.encoding, addr_size_bytes); + var left: usize = 0; + var len: usize = table.fde_count; + while (len > 1) { + const mid = left + len / 2; + var entry_reader: Reader = .fixed(table.entries[mid * entry_size ..][0..entry_size]); + const pc_begin = try readEhPointer(&entry_reader, table.encoding, addr_size_bytes, .{ + .pc_rel_base = table_vaddr + left * entry_size, + .data_rel_base = eh_frame_hdr_vaddr, + }, endian); + if (pc < pc_begin) { + len /= 2; + } else { + left = mid; + len -= len / 2; + } + } + if (len == 0) return null; + var entry_reader: Reader = .fixed(table.entries[left * entry_size ..][0..entry_size]); + // Skip past `pc_begin`; we're now interested in the fde offset + _ = try readEhPointerAbs(&entry_reader, table.encoding.type, addr_size_bytes, endian); + const fde_ptr = try readEhPointer(&entry_reader, table.encoding, addr_size_bytes, .{ + .pc_rel_base = table_vaddr + left * entry_size, + .data_rel_base = eh_frame_hdr_vaddr, + }, endian); + return fde_ptr; + } + + fn entrySize(table_enc: EH.PE, addr_size_bytes: u8) !u8 { + return switch (table_enc.type) { + .absptr => 2 * addr_size_bytes, + .udata2, .sdata2 => 4, + .udata4, .sdata4 => 8, + .udata8, .sdata8 => 16, + .uleb128, .sleb128 => return bad(), // this is a binary search table; all entries must be the same size + _ => return bad(), + }; + } + }; + + pub fn parse( + eh_frame_hdr_vaddr: u64, + eh_frame_hdr_bytes: []const u8, + addr_size_bytes: u8, + endian: Endian, + ) !EhFrameHeader { + var r: Reader = .fixed(eh_frame_hdr_bytes); + + const version = try r.takeByte(); + if (version != 1) return bad(); + + const eh_frame_ptr_enc: EH.PE = @bitCast(try r.takeByte()); + const fde_count_enc: EH.PE = @bitCast(try r.takeByte()); + const table_enc: EH.PE = @bitCast(try r.takeByte()); + + const eh_frame_ptr = try readEhPointer(&r, eh_frame_ptr_enc, addr_size_bytes, .{ + .pc_rel_base = eh_frame_hdr_vaddr + r.seek, + }, endian); + + const table: ?SearchTable = table: { + if (fde_count_enc == EH.PE.omit) break :table null; + if (table_enc == EH.PE.omit) break :table null; + const fde_count = try readEhPointer(&r, fde_count_enc, addr_size_bytes, .{ + .pc_rel_base = eh_frame_hdr_vaddr + r.seek, + }, endian); + const entry_size = try SearchTable.entrySize(table_enc, addr_size_bytes); + const bytes_offset = r.seek; + const bytes_len = cast(usize, fde_count * entry_size) orelse return error.EndOfStream; + const bytes = try r.take(bytes_len); + break :table .{ + .encoding = table_enc, + .fde_count = @intCast(fde_count), + .entries = bytes, + .offset = @intCast(bytes_offset), + }; + }; + + return .{ + .eh_frame_vaddr = eh_frame_ptr, + .search_table = table, + }; + } +}; + +/// The shared header of an FDE/CIE, containing a length in bytes (DWARF's "initial length field") +/// and a value which differentiates CIEs from FDEs and maps FDEs to their corresponding CIEs. The +/// `.eh_frame` format also includes a third variation, here called `.terminator`, which acts as a +/// sentinel for the whole section. +/// +/// `CommonInformationEntry.parse` and `FrameDescriptionEntry.parse` expect the `EntryHeader` to +/// have been parsed first: they accept data stored in the `EntryHeader`, and only read the bytes +/// following this header. +const EntryHeader = union(enum) { + cie: struct { + format: Format, + /// Remaining bytes in the CIE. These are parseable by `CommonInformationEntry.parse`. + bytes_len: u64, + }, + fde: struct { + /// Offset into the section of the corresponding CIE, *including* its entry header. + cie_offset: u64, + /// Remaining bytes in the FDE. These are parseable by `FrameDescriptionEntry.parse`. + bytes_len: u64, + }, + /// The `.eh_frame` format includes terminators which indicate that the last CIE/FDE has been + /// reached. However, `.debug_frame` does not include such a terminator, so the caller must + /// keep track of how many section bytes remain when parsing all entries in `.debug_frame`. + terminator, + + fn read(r: *Reader, header_section_offset: u64, section: Section, endian: Endian) !EntryHeader { + const unit_header = try Dwarf.readUnitHeader(r, endian); + if (unit_header.unit_length == 0) return .terminator; + + // Next is a value which will disambiguate CIEs and FDEs. Annoyingly, LSB Core makes this + // value always 4-byte, whereas DWARF makes it depend on the `dwarf.Format`. + const cie_ptr_or_id_size: u8 = switch (section) { + .eh_frame => 4, + .debug_frame => switch (unit_header.format) { + .@"32" => 4, + .@"64" => 8, + }, + }; + const cie_ptr_or_id = switch (cie_ptr_or_id_size) { + 4 => try r.takeInt(u32, endian), + 8 => try r.takeInt(u64, endian), + else => unreachable, + }; + const remaining_bytes = unit_header.unit_length - cie_ptr_or_id_size; + + // If this entry is a CIE, then `cie_ptr_or_id` will have this value, which is different + // between the DWARF `.debug_frame` section and the LSB Core `.eh_frame` section. + const cie_id: u64 = switch (section) { + .eh_frame => 0, + .debug_frame => switch (unit_header.format) { + .@"32" => maxInt(u32), + .@"64" => maxInt(u64), + }, + }; + if (cie_ptr_or_id == cie_id) { + return .{ .cie = .{ + .format = unit_header.format, + .bytes_len = remaining_bytes, + } }; + } + + // This is an FDE -- `cie_ptr_or_id` points to the associated CIE. Unfortunately, the format + // of that pointer again differs between `.debug_frame` and `.eh_frame`. + const cie_offset = switch (section) { + .eh_frame => try std.math.sub(u64, header_section_offset + unit_header.header_length, cie_ptr_or_id), + .debug_frame => cie_ptr_or_id, + }; + return .{ .fde = .{ + .cie_offset = cie_offset, + .bytes_len = remaining_bytes, + } }; + } +}; + +pub const CommonInformationEntry = struct { + version: u8, + format: Format, + + /// In version 4, CIEs can specify the address size used in the CIE and associated FDEs. + /// This value must be used *only* to parse associated FDEs in `FrameDescriptionEntry.parse`. + addr_size_bytes: u8, + + /// Always 0 for versions which do not specify this (currently all versions other than 4). + segment_selector_size: u8, + + code_alignment_factor: u32, + data_alignment_factor: i32, + return_address_register: u8, + + fde_pointer_enc: EH.PE, + is_signal_frame: bool, + + augmentation_kind: AugmentationKind, + + initial_instructions: []const u8, + + last_row: ?struct { + offset: u64, + cfa: VirtualMachine.CfaRule, + cols: []VirtualMachine.Column, + }, + + pub const AugmentationKind = enum { none, gcc_eh, lsb_z }; + + /// This function expects to read the CIE starting with the version field. + /// The returned struct references memory backed by `cie_bytes`. + /// + /// `length_offset` specifies the offset of this CIE's length field in the + /// .eh_frame / .debug_frame section. + fn parse( + format: Format, + cie_bytes: []const u8, + section: Section, + default_addr_size_bytes: u8, + ) !CommonInformationEntry { + // We only read the data through this reader. + var r: Reader = .fixed(cie_bytes); + + const version = try r.takeByte(); + switch (section) { + .eh_frame => if (version != 1 and version != 3) return error.UnsupportedDwarfVersion, + .debug_frame => if (version != 4) return error.UnsupportedDwarfVersion, + } + + const aug_str = try r.takeSentinel(0); + const aug_kind: AugmentationKind = aug: { + if (aug_str.len == 0) break :aug .none; + if (aug_str[0] == 'z') break :aug .lsb_z; + if (std.mem.eql(u8, aug_str, "eh")) break :aug .gcc_eh; + // We can't finish parsing the CIE if we don't know what its augmentation means. + return bad(); + }; + + switch (aug_kind) { + .none => {}, // no extra data + .lsb_z => {}, // no extra data yet, but there is a bit later + .gcc_eh => try r.discardAll(default_addr_size_bytes), // unsupported data + } + + const addr_size_bytes = if (version == 4) try r.takeByte() else default_addr_size_bytes; + const segment_selector_size: u8 = if (version == 4) try r.takeByte() else 0; + const code_alignment_factor = try r.takeLeb128(u32); + const data_alignment_factor = try r.takeLeb128(i32); + const return_address_register = if (version == 1) try r.takeByte() else try r.takeLeb128(u8); + + // This is where LSB's augmentation might add some data. + const fde_pointer_enc: EH.PE, const is_signal_frame: bool = aug: { + const default_fde_pointer_enc: EH.PE = .{ .type = .absptr, .rel = .abs }; + if (aug_kind != .lsb_z) break :aug .{ default_fde_pointer_enc, false }; + const aug_data_len = try r.takeLeb128(u32); + var aug_data: Reader = .fixed(try r.take(aug_data_len)); + var fde_pointer_enc: EH.PE = default_fde_pointer_enc; + var is_signal_frame = false; + for (aug_str[1..]) |byte| switch (byte) { + 'L' => _ = try aug_data.takeByte(), // we ignore the LSDA pointer + 'P' => { + const enc: EH.PE = @bitCast(try aug_data.takeByte()); + const endian: Endian = .little; // irrelevant because we're discarding the value anyway + _ = try readEhPointerAbs(&aug_data, enc.type, addr_size_bytes, endian); // we ignore the personality routine; endianness is irrelevant since we're discarding + }, + 'R' => fde_pointer_enc = @bitCast(try aug_data.takeByte()), + 'S' => is_signal_frame = true, + 'B', 'G' => {}, + else => return bad(), + }; + break :aug .{ fde_pointer_enc, is_signal_frame }; + }; + + return .{ + .format = format, + .version = version, + .addr_size_bytes = addr_size_bytes, + .segment_selector_size = segment_selector_size, + .code_alignment_factor = code_alignment_factor, + .data_alignment_factor = data_alignment_factor, + .return_address_register = return_address_register, + .fde_pointer_enc = fde_pointer_enc, + .is_signal_frame = is_signal_frame, + .augmentation_kind = aug_kind, + .initial_instructions = r.buffered(), + .last_row = null, + }; + } +}; + +pub const FrameDescriptionEntry = struct { + pc_begin: u64, + pc_range: u64, + instructions: []const u8, + + /// This function expects to read the FDE starting at the PC Begin field. + /// The returned struct references memory backed by `fde_bytes`. + fn parse( + /// The virtual address of the FDE we're parsing, *excluding* its entry header (i.e. the + /// address is after the header). If `fde_bytes` is backed by the memory of a loaded + /// module's `.eh_frame` section, this will equal `fde_bytes.ptr`. + fde_vaddr: u64, + fde_bytes: []const u8, + cie: *const CommonInformationEntry, + endian: Endian, + ) !FrameDescriptionEntry { + if (cie.segment_selector_size != 0) return error.UnsupportedAddrSize; + + var r: Reader = .fixed(fde_bytes); + + const pc_begin = try readEhPointer(&r, cie.fde_pointer_enc, cie.addr_size_bytes, .{ + .pc_rel_base = fde_vaddr, + }, endian); + + // I swear I'm not kidding when I say that PC Range is encoded with `cie.fde_pointer_enc`, but ignoring `rel`. + const pc_range = switch (try readEhPointerAbs(&r, cie.fde_pointer_enc.type, cie.addr_size_bytes, endian)) { + .unsigned => |x| x, + .signed => |x| cast(u64, x) orelse return bad(), + }; + + switch (cie.augmentation_kind) { + .none, .gcc_eh => {}, + .lsb_z => { + // There is augmentation data, but it's irrelevant to us -- it + // only contains the LSDA pointer, which we don't care about. + const aug_data_len = try r.takeLeb128(usize); + _ = try r.discardAll(aug_data_len); + }, + } + + return .{ + .pc_begin = pc_begin, + .pc_range = pc_range, + .instructions = r.buffered(), + }; + } +}; + +/// Builds the CIE list and FDE lookup table if they are not already built. It is required to call +/// this function at least once before calling `lookupPc` or `getFde`. If only `getFde` is needed, +/// then `need_lookup` can be set to `false` to make this function more efficient. +pub fn prepare( + unwind: *Unwind, + gpa: Allocator, + addr_size_bytes: u8, + endian: Endian, + need_lookup: bool, + /// The `__eh_frame` section in Mach-O binaries deviates from the standard `.eh_frame` section + /// in one way which this function needs to be aware of. + is_macho: bool, +) !void { + if (unwind.cie_list.len > 0 and (!need_lookup or unwind.lookup != null)) return; + unwind.cie_list.clearRetainingCapacity(); + + if (is_macho) assert(unwind.lookup == null or unwind.lookup.? != .eh_frame_hdr); + + const section = unwind.frame_section; + + var r: Reader = .fixed(section.bytes); + var fde_list: std.ArrayList(SortedFdeEntry) = .empty; + defer fde_list.deinit(gpa); + + const saw_terminator = while (r.seek < r.buffer.len) { + const entry_offset = r.seek; + switch (try EntryHeader.read(&r, entry_offset, section.id, endian)) { + .cie => |cie_info| { + // We will pre-populate a list of CIEs for efficiency: this avoids work re-parsing + // them every time we look up an FDE. It also lets us cache the result of evaluating + // the CIE's initial CFI instructions, which is useful because in the vast majority + // of cases those instructions will be needed to reach the PC we are unwinding to. + const bytes_len = cast(usize, cie_info.bytes_len) orelse return error.EndOfStream; + const idx = unwind.cie_list.len; + try unwind.cie_list.append(gpa, .{ + .offset = entry_offset, + .cie = try .parse(cie_info.format, try r.take(bytes_len), section.id, addr_size_bytes), + }); + errdefer _ = unwind.cie_list.pop().?; + try VirtualMachine.populateCieLastRow(gpa, &unwind.cie_list.items(.cie)[idx], addr_size_bytes, endian); + continue; + }, + .fde => |fde_info| { + const bytes_len = cast(usize, fde_info.bytes_len) orelse return error.EndOfStream; + if (!need_lookup) { + try r.discardAll(bytes_len); + continue; + } + const cie = unwind.findCie(fde_info.cie_offset) orelse return error.InvalidDebugInfo; + const fde: FrameDescriptionEntry = try .parse(section.vaddr + r.seek, try r.take(bytes_len), cie, endian); + try fde_list.append(gpa, .{ + .pc_begin = fde.pc_begin, + .fde_offset = entry_offset, + }); + }, + .terminator => break true, + } + } else false; + const expect_terminator = switch (section.id) { + .eh_frame => !is_macho, // `.eh_frame` indicates the end of the CIE/FDE list with a sentinel entry, though macOS omits this + .debug_frame => false, // `.debug_frame` uses the section bounds and does not specify a sentinel entry + }; + if (saw_terminator != expect_terminator) return bad(); + + if (need_lookup) { + std.mem.sortUnstable(SortedFdeEntry, fde_list.items, {}, struct { + fn lessThan(ctx: void, a: SortedFdeEntry, b: SortedFdeEntry) bool { + ctx; + return a.pc_begin < b.pc_begin; + } + }.lessThan); + + // This temporary is necessary to avoid an RLS footgun where `lookup` ends up non-null `undefined` on OOM. + const final_fdes = try fde_list.toOwnedSlice(gpa); + unwind.lookup = .{ .sorted_fdes = final_fdes }; + } +} + +fn findCie(unwind: *const Unwind, offset: u64) ?*const CommonInformationEntry { + const offsets = unwind.cie_list.items(.offset); + if (offsets.len == 0) return null; + var start: usize = 0; + var len: usize = offsets.len; + while (len > 1) { + const mid = len / 2; + if (offset < offsets[start + mid]) { + len = mid; + } else { + start += mid; + len -= mid; + } + } + if (offsets[start] != offset) return null; + return &unwind.cie_list.items(.cie)[start]; +} + +/// Given a program counter value, returns the offset of the corresponding FDE, or `null` if no +/// matching FDE was found. The returned offset can be passed to `getFde` to load the data +/// associated with the FDE. +/// +/// Before calling this function, `prepare` must return successfully at least once, to ensure that +/// `unwind.lookup` is populated. +/// +/// The return value may be a false positive. After loading the FDE with `loadFde`, the caller must +/// validate that `pc` is indeed in its range -- if it is not, then no FDE matches `pc`. +pub fn lookupPc(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian: Endian) !?u64 { + const sorted_fdes: []const SortedFdeEntry = switch (unwind.lookup.?) { + .eh_frame_hdr => |eh_frame_hdr| { + const fde_vaddr = try eh_frame_hdr.table.findEntry( + eh_frame_hdr.vaddr, + pc, + addr_size_bytes, + endian, + ) orelse return null; + return std.math.sub(u64, fde_vaddr, unwind.frame_section.vaddr) catch bad(); // convert vaddr to offset + }, + .sorted_fdes => |sorted_fdes| sorted_fdes, + }; + if (sorted_fdes.len == 0) return null; + var start: usize = 0; + var len: usize = sorted_fdes.len; + while (len > 1) { + const half = len / 2; + if (pc < sorted_fdes[start + half].pc_begin) { + len = half; + } else { + start += half; + len -= half; + } + } + // If any FDE matches, it'll be the one at `start` (maybe false positive). + return sorted_fdes[start].fde_offset; +} + +/// Get the FDE at a given offset, as well as its associated CIE. This offset typically comes from +/// `lookupPc`. The CFI instructions within can be evaluated with `VirtualMachine`. +pub fn getFde(unwind: *const Unwind, fde_offset: u64, endian: Endian) !struct { *const CommonInformationEntry, FrameDescriptionEntry } { + const section = unwind.frame_section; + + if (fde_offset > section.bytes.len) return error.EndOfStream; + var fde_reader: Reader = .fixed(section.bytes[@intCast(fde_offset)..]); + const fde_info = switch (try EntryHeader.read(&fde_reader, fde_offset, section.id, endian)) { + .fde => |info| info, + .cie, .terminator => return bad(), // This is meant to be an FDE + }; + + const cie = unwind.findCie(fde_info.cie_offset) orelse return error.InvalidDebugInfo; + const fde: FrameDescriptionEntry = try .parse( + section.vaddr + fde_offset + fde_reader.seek, + try fde_reader.take(cast(usize, fde_info.bytes_len) orelse return error.EndOfStream), + cie, + endian, + ); + + return .{ cie, fde }; +} + +const EhPointerContext = struct { + /// The address of the pointer field itself + pc_rel_base: u64, + // These relative addressing modes are only used in specific cases, and + // might not be available / required in all parsing contexts + data_rel_base: ?u64 = null, + text_rel_base: ?u64 = null, + function_rel_base: ?u64 = null, +}; +/// Returns `error.InvalidDebugInfo` if the encoding is `EH.PE.omit`. +fn readEhPointerAbs(r: *Reader, enc_ty: EH.PE.Type, addr_size_bytes: u8, endian: Endian) !union(enum) { + signed: i64, + unsigned: u64, +} { + return switch (enc_ty) { + .absptr => .{ + .unsigned = switch (addr_size_bytes) { + 2 => try r.takeInt(u16, endian), + 4 => try r.takeInt(u32, endian), + 8 => try r.takeInt(u64, endian), + else => return error.UnsupportedAddrSize, + }, + }, + .uleb128 => .{ .unsigned = try r.takeLeb128(u64) }, + .udata2 => .{ .unsigned = try r.takeInt(u16, endian) }, + .udata4 => .{ .unsigned = try r.takeInt(u32, endian) }, + .udata8 => .{ .unsigned = try r.takeInt(u64, endian) }, + .sleb128 => .{ .signed = try r.takeLeb128(i64) }, + .sdata2 => .{ .signed = try r.takeInt(i16, endian) }, + .sdata4 => .{ .signed = try r.takeInt(i32, endian) }, + .sdata8 => .{ .signed = try r.takeInt(i64, endian) }, + else => return bad(), + }; +} +/// Returns `error.InvalidDebugInfo` if the encoding is `EH.PE.omit`. +fn readEhPointer(r: *Reader, enc: EH.PE, addr_size_bytes: u8, ctx: EhPointerContext, endian: Endian) !u64 { + const offset = try readEhPointerAbs(r, enc.type, addr_size_bytes, endian); + if (enc.indirect) return bad(); // GCC extension; not supported + const base: u64 = switch (enc.rel) { + .abs, .aligned => 0, + .pcrel => ctx.pc_rel_base, + .textrel => ctx.text_rel_base orelse return bad(), + .datarel => ctx.data_rel_base orelse return bad(), + .funcrel => ctx.function_rel_base orelse return bad(), + _ => return bad(), + }; + return switch (offset) { + .signed => |s| if (s >= 0) + try std.math.add(u64, base, @intCast(s)) + else + try std.math.sub(u64, base, @intCast(-s)), + // absptr can actually contain signed values in some cases (aarch64 MachO) + .unsigned => |u| u +% base, + }; +} + +/// Like `Reader.fixed`, but when the length of the data is unknown and we just want to allow +/// reading indefinitely. +fn maxSlice(ptr: [*]const u8) []const u8 { + const len = std.math.maxInt(usize) - @intFromPtr(ptr); + return ptr[0..len]; +} + +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const bad = Dwarf.bad; +const cast = std.math.cast; +const DW = std.dwarf; +const Dwarf = std.debug.Dwarf; +const EH = DW.EH; +const Endian = std.builtin.Endian; +const Format = DW.Format; +const maxInt = std.math.maxInt; +const missing = Dwarf.missing; +const Reader = std.Io.Reader; +const std = @import("std"); +const Unwind = @This(); diff --git a/lib/std/debug/Dwarf/Unwind/VirtualMachine.zig b/lib/std/debug/Dwarf/Unwind/VirtualMachine.zig new file mode 100644 index 0000000000..ccb674b565 --- /dev/null +++ b/lib/std/debug/Dwarf/Unwind/VirtualMachine.zig @@ -0,0 +1,470 @@ +//! Virtual machine that evaluates DWARF call frame instructions + +/// See section 6.4.1 of the DWARF5 specification for details on each +pub const RegisterRule = union(enum) { + /// The spec says that the default rule for each column is the undefined rule. + /// However, it also allows ABI / compiler authors to specify alternate defaults, so + /// there is a distinction made here. + default, + undefined, + same_value, + /// offset(N) + offset: i64, + /// val_offset(N) + val_offset: i64, + /// register(R) + register: u8, + /// expression(E) + expression: []const u8, + /// val_expression(E) + val_expression: []const u8, +}; + +pub const CfaRule = union(enum) { + none, + reg_off: struct { + register: u8, + offset: i64, + }, + expression: []const u8, +}; + +/// Each row contains unwinding rules for a set of registers. +pub const Row = struct { + /// Offset from `FrameDescriptionEntry.pc_begin` + offset: u64 = 0, + cfa: CfaRule = .none, + /// The register fields in these columns define the register the rule applies to. + columns: ColumnRange = .{ .start = undefined, .len = 0 }, +}; + +pub const Column = struct { + register: u8, + rule: RegisterRule, +}; + +const ColumnRange = struct { + start: usize, + len: u8, +}; + +columns: std.ArrayList(Column) = .empty, +stack: std.ArrayList(struct { + cfa: CfaRule, + columns: ColumnRange, +}) = .empty, +current_row: Row = .{}, + +/// The result of executing the CIE's initial_instructions +cie_row: ?Row = null, + +pub fn deinit(self: *VirtualMachine, gpa: Allocator) void { + self.stack.deinit(gpa); + self.columns.deinit(gpa); + self.* = undefined; +} + +pub fn reset(self: *VirtualMachine) void { + self.stack.clearRetainingCapacity(); + self.columns.clearRetainingCapacity(); + self.current_row = .{}; + self.cie_row = null; +} + +/// Return a slice backed by the row's non-CFA columns +pub fn rowColumns(self: *const VirtualMachine, row: *const Row) []Column { + if (row.columns.len == 0) return &.{}; + return self.columns.items[row.columns.start..][0..row.columns.len]; +} + +/// Either retrieves or adds a column for `register` (non-CFA) in the current row. +fn getOrAddColumn(self: *VirtualMachine, gpa: Allocator, register: u8) !*Column { + for (self.rowColumns(&self.current_row)) |*c| { + if (c.register == register) return c; + } + + if (self.current_row.columns.len == 0) { + self.current_row.columns.start = self.columns.items.len; + } else { + assert(self.current_row.columns.start + self.current_row.columns.len == self.columns.items.len); + } + self.current_row.columns.len += 1; + + const column = try self.columns.addOne(gpa); + column.* = .{ + .register = register, + .rule = .default, + }; + + return column; +} + +pub fn populateCieLastRow( + gpa: Allocator, + cie: *Unwind.CommonInformationEntry, + addr_size_bytes: u8, + endian: std.builtin.Endian, +) !void { + assert(cie.last_row == null); + + var vm: VirtualMachine = .{}; + defer vm.deinit(gpa); + + try vm.evalInstructions( + gpa, + cie, + std.math.maxInt(u64), + cie.initial_instructions, + addr_size_bytes, + endian, + ); + + cie.last_row = .{ + .offset = vm.current_row.offset, + .cfa = vm.current_row.cfa, + .cols = try gpa.dupe(Column, vm.rowColumns(&vm.current_row)), + }; +} + +/// Runs the CIE instructions, then the FDE instructions. Execution halts +/// once the row that corresponds to `pc` is known, and the row is returned. +pub fn runTo( + vm: *VirtualMachine, + gpa: Allocator, + pc: u64, + cie: *const Unwind.CommonInformationEntry, + fde: *const Unwind.FrameDescriptionEntry, + addr_size_bytes: u8, + endian: std.builtin.Endian, +) !Row { + assert(vm.cie_row == null); + + const target_offset = pc - fde.pc_begin; + assert(target_offset < fde.pc_range); + + const instruction_bytes: []const u8 = insts: { + if (target_offset < cie.last_row.?.offset) { + break :insts cie.initial_instructions; + } + // This is the more common case: start from the CIE's last row. + assert(vm.columns.items.len == 0); + vm.current_row = .{ + .offset = cie.last_row.?.offset, + .cfa = cie.last_row.?.cfa, + .columns = .{ + .start = 0, + .len = @intCast(cie.last_row.?.cols.len), + }, + }; + try vm.columns.appendSlice(gpa, cie.last_row.?.cols); + vm.cie_row = vm.current_row; + break :insts fde.instructions; + }; + + try vm.evalInstructions( + gpa, + cie, + target_offset, + instruction_bytes, + addr_size_bytes, + endian, + ); + return vm.current_row; +} + +/// Evaluates instructions from `instruction_bytes` until `target_addr` is reached or all +/// instructions have been evaluated. +fn evalInstructions( + vm: *VirtualMachine, + gpa: Allocator, + cie: *const Unwind.CommonInformationEntry, + target_addr: u64, + instruction_bytes: []const u8, + addr_size_bytes: u8, + endian: std.builtin.Endian, +) !void { + var fr: std.Io.Reader = .fixed(instruction_bytes); + while (fr.seek < fr.buffer.len) { + switch (try Instruction.read(&fr, addr_size_bytes, endian)) { + .nop => { + // If there was one nop, there's a good chance we've reached the padding and so + // everything left is a nop, which is represented by a 0 byte. + if (std.mem.allEqual(u8, fr.buffered(), 0)) return; + }, + + .remember_state => { + try vm.stack.append(gpa, .{ + .cfa = vm.current_row.cfa, + .columns = vm.current_row.columns, + }); + const cols_len = vm.current_row.columns.len; + const copy_start = vm.columns.items.len; + assert(vm.current_row.columns.start == copy_start - cols_len); + try vm.columns.ensureUnusedCapacity(gpa, cols_len); // to prevent aliasing issues + vm.columns.appendSliceAssumeCapacity(vm.columns.items[copy_start - cols_len ..]); + vm.current_row.columns.start = copy_start; + }, + .restore_state => { + const restored = vm.stack.pop() orelse return error.InvalidOperation; + vm.columns.shrinkRetainingCapacity(restored.columns.start + restored.columns.len); + + vm.current_row.cfa = restored.cfa; + vm.current_row.columns = restored.columns; + }, + + .advance_loc => |delta| { + const new_addr = vm.current_row.offset + delta * cie.code_alignment_factor; + if (new_addr > target_addr) return; + vm.current_row.offset = new_addr; + }, + .set_loc => |new_addr| { + if (new_addr <= vm.current_row.offset) return error.InvalidOperation; + if (cie.segment_selector_size != 0) return error.InvalidOperation; // unsupported + // TODO: Check cie.segment_selector_size != 0 for DWARFV4 + + if (new_addr > target_addr) return; + vm.current_row.offset = new_addr; + }, + + .register => |reg| { + const column = try vm.getOrAddColumn(gpa, reg.index); + column.rule = switch (reg.rule) { + .restore => rule: { + const cie_row = &(vm.cie_row orelse return error.InvalidOperation); + for (vm.rowColumns(cie_row)) |cie_col| { + if (cie_col.register == reg.index) break :rule cie_col.rule; + } + break :rule .default; + }, + .undefined => .undefined, + .same_value => .same_value, + .offset_uf => |off| .{ .offset = @as(i64, @intCast(off)) * cie.data_alignment_factor }, + .offset_sf => |off| .{ .offset = off * cie.data_alignment_factor }, + .val_offset_uf => |off| .{ .val_offset = @as(i64, @intCast(off)) * cie.data_alignment_factor }, + .val_offset_sf => |off| .{ .val_offset = off * cie.data_alignment_factor }, + .register => |callee_reg| .{ .register = callee_reg }, + .expr => |len| .{ .expression = try takeExprBlock(&fr, len) }, + .val_expr => |len| .{ .val_expression = try takeExprBlock(&fr, len) }, + }; + }, + .def_cfa => |cfa| vm.current_row.cfa = .{ .reg_off = .{ + .register = cfa.register, + .offset = @intCast(cfa.offset), + } }, + .def_cfa_sf => |cfa| vm.current_row.cfa = .{ .reg_off = .{ + .register = cfa.register, + .offset = cfa.offset_sf * cie.data_alignment_factor, + } }, + .def_cfa_reg => |register| switch (vm.current_row.cfa) { + .none => { + // According to the DWARF specification, this is not valid, because this + // instruction can only be used to replace the register if the rule is already a + // `.reg_off`. However, this is emitted in practice by GNU toolchains for some + // targets, and so by convention is interpreted as equivalent to `.def_cfa` with + // an offset of 0. + vm.current_row.cfa = .{ .reg_off = .{ + .register = register, + .offset = 0, + } }; + }, + .expression => return error.InvalidOperation, + .reg_off => |*ro| ro.register = register, + }, + .def_cfa_offset => |offset| switch (vm.current_row.cfa) { + .none, .expression => return error.InvalidOperation, + .reg_off => |*ro| ro.offset = @intCast(offset), + }, + .def_cfa_offset_sf => |offset_sf| switch (vm.current_row.cfa) { + .none, .expression => return error.InvalidOperation, + .reg_off => |*ro| ro.offset = offset_sf * cie.data_alignment_factor, + }, + .def_cfa_expr => |len| { + vm.current_row.cfa = .{ .expression = try takeExprBlock(&fr, len) }; + }, + } + } +} + +fn takeExprBlock(r: *std.Io.Reader, len: usize) error{ ReadFailed, InvalidOperand }![]const u8 { + return r.take(len) catch |err| switch (err) { + error.ReadFailed => |e| return e, + error.EndOfStream => return error.InvalidOperand, + }; +} + +const OpcodeByte = packed struct(u8) { + low: packed union { + operand: u6, + extended: enum(u6) { + nop = 0, + set_loc = 1, + advance_loc1 = 2, + advance_loc2 = 3, + advance_loc4 = 4, + offset_extended = 5, + restore_extended = 6, + undefined = 7, + same_value = 8, + register = 9, + remember_state = 10, + restore_state = 11, + def_cfa = 12, + def_cfa_register = 13, + def_cfa_offset = 14, + def_cfa_expression = 15, + expression = 16, + offset_extended_sf = 17, + def_cfa_sf = 18, + def_cfa_offset_sf = 19, + val_offset = 20, + val_offset_sf = 21, + val_expression = 22, + _, + }, + }, + opcode: enum(u2) { + extended = 0, + advance_loc = 1, + offset = 2, + restore = 3, + }, +}; + +pub const Instruction = union(enum) { + nop, + remember_state, + restore_state, + advance_loc: u32, + set_loc: u64, + + register: struct { + index: u8, + rule: union(enum) { + restore, // restore from cie + undefined, + same_value, + offset_uf: u64, + offset_sf: i64, + val_offset_uf: u64, + val_offset_sf: i64, + register: u8, + /// Value is the number of bytes in the DWARF expression, which the caller must read. + expr: usize, + /// Value is the number of bytes in the DWARF expression, which the caller must read. + val_expr: usize, + }, + }, + + def_cfa: struct { + register: u8, + offset: u64, + }, + def_cfa_sf: struct { + register: u8, + offset_sf: i64, + }, + def_cfa_reg: u8, + def_cfa_offset: u64, + def_cfa_offset_sf: i64, + /// Value is the number of bytes in the DWARF expression, which the caller must read. + def_cfa_expr: usize, + + pub fn read( + reader: *std.Io.Reader, + addr_size_bytes: u8, + endian: std.builtin.Endian, + ) !Instruction { + const inst: OpcodeByte = @bitCast(try reader.takeByte()); + return switch (inst.opcode) { + .advance_loc => .{ .advance_loc = inst.low.operand }, + .offset => .{ .register = .{ + .index = inst.low.operand, + .rule = .{ .offset_uf = try reader.takeLeb128(u64) }, + } }, + .restore => .{ .register = .{ + .index = inst.low.operand, + .rule = .restore, + } }, + .extended => switch (inst.low.extended) { + .nop => .nop, + .remember_state => .remember_state, + .restore_state => .restore_state, + .advance_loc1 => .{ .advance_loc = try reader.takeByte() }, + .advance_loc2 => .{ .advance_loc = try reader.takeInt(u16, endian) }, + .advance_loc4 => .{ .advance_loc = try reader.takeInt(u32, endian) }, + .set_loc => .{ .set_loc = switch (addr_size_bytes) { + 2 => try reader.takeInt(u16, endian), + 4 => try reader.takeInt(u32, endian), + 8 => try reader.takeInt(u64, endian), + else => return error.UnsupportedAddrSize, + } }, + + .offset_extended => .{ .register = .{ + .index = try reader.takeLeb128(u8), + .rule = .{ .offset_uf = try reader.takeLeb128(u64) }, + } }, + .offset_extended_sf => .{ .register = .{ + .index = try reader.takeLeb128(u8), + .rule = .{ .offset_sf = try reader.takeLeb128(i64) }, + } }, + .restore_extended => .{ .register = .{ + .index = try reader.takeLeb128(u8), + .rule = .restore, + } }, + .undefined => .{ .register = .{ + .index = try reader.takeLeb128(u8), + .rule = .undefined, + } }, + .same_value => .{ .register = .{ + .index = try reader.takeLeb128(u8), + .rule = .same_value, + } }, + .register => .{ .register = .{ + .index = try reader.takeLeb128(u8), + .rule = .{ .register = try reader.takeLeb128(u8) }, + } }, + .val_offset => .{ .register = .{ + .index = try reader.takeLeb128(u8), + .rule = .{ .val_offset_uf = try reader.takeLeb128(u64) }, + } }, + .val_offset_sf => .{ .register = .{ + .index = try reader.takeLeb128(u8), + .rule = .{ .val_offset_sf = try reader.takeLeb128(i64) }, + } }, + .expression => .{ .register = .{ + .index = try reader.takeLeb128(u8), + .rule = .{ .expr = try reader.takeLeb128(usize) }, + } }, + .val_expression => .{ .register = .{ + .index = try reader.takeLeb128(u8), + .rule = .{ .val_expr = try reader.takeLeb128(usize) }, + } }, + + .def_cfa => .{ .def_cfa = .{ + .register = try reader.takeLeb128(u8), + .offset = try reader.takeLeb128(u64), + } }, + .def_cfa_sf => .{ .def_cfa_sf = .{ + .register = try reader.takeLeb128(u8), + .offset_sf = try reader.takeLeb128(i64), + } }, + .def_cfa_register => .{ .def_cfa_reg = try reader.takeLeb128(u8) }, + .def_cfa_offset => .{ .def_cfa_offset = try reader.takeLeb128(u64) }, + .def_cfa_offset_sf => .{ .def_cfa_offset_sf = try reader.takeLeb128(i64) }, + .def_cfa_expression => .{ .def_cfa_expr = try reader.takeLeb128(usize) }, + + _ => switch (@intFromEnum(inst.low.extended)) { + 0x1C...0x3F => return error.UnimplementedUserOpcode, + else => return error.InvalidOpcode, + }, + }, + }; + } +}; + +const std = @import("../../../std.zig"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const Unwind = std.debug.Dwarf.Unwind; + +const VirtualMachine = @This(); diff --git a/lib/std/debug/Dwarf/abi.zig b/lib/std/debug/Dwarf/abi.zig deleted file mode 100644 index c5e509c7b0..0000000000 --- a/lib/std/debug/Dwarf/abi.zig +++ /dev/null @@ -1,351 +0,0 @@ -const builtin = @import("builtin"); - -const std = @import("../../std.zig"); -const mem = std.mem; -const posix = std.posix; -const Arch = std.Target.Cpu.Arch; - -/// Tells whether unwinding for this target is supported by the Dwarf standard. -/// -/// See also `std.debug.SelfInfo.supportsUnwinding` which tells whether the Zig -/// standard library has a working implementation of unwinding for this target. -pub fn supportsUnwinding(target: *const std.Target) bool { - return switch (target.cpu.arch) { - .amdgcn, - .nvptx, - .nvptx64, - .spirv32, - .spirv64, - => false, - - // Enabling this causes relocation errors such as: - // error: invalid relocation type R_RISCV_SUB32 at offset 0x20 - .riscv64, .riscv64be, .riscv32, .riscv32be => false, - - // Conservative guess. Feel free to update this logic with any targets - // that are known to not support Dwarf unwinding. - else => true, - }; -} - -/// Returns `null` for CPU architectures without an instruction pointer register. -pub fn ipRegNum(arch: Arch) ?u8 { - return switch (arch) { - .x86 => 8, - .x86_64 => 16, - .arm, .armeb, .thumb, .thumbeb => 15, - .aarch64, .aarch64_be => 32, - else => null, - }; -} - -pub fn fpRegNum(arch: Arch, reg_context: RegisterContext) u8 { - return switch (arch) { - // GCC on OS X historically did the opposite of ELF for these registers - // (only in .eh_frame), and that is now the convention for MachO - .x86 => if (reg_context.eh_frame and reg_context.is_macho) 4 else 5, - .x86_64 => 6, - .arm, .armeb, .thumb, .thumbeb => 11, - .aarch64, .aarch64_be => 29, - else => unreachable, - }; -} - -pub fn spRegNum(arch: Arch, reg_context: RegisterContext) u8 { - return switch (arch) { - .x86 => if (reg_context.eh_frame and reg_context.is_macho) 5 else 4, - .x86_64 => 7, - .arm, .armeb, .thumb, .thumbeb => 13, - .aarch64, .aarch64_be => 31, - else => unreachable, - }; -} - -pub const RegisterContext = struct { - eh_frame: bool, - is_macho: bool, -}; - -pub const RegBytesError = error{ - InvalidRegister, - UnimplementedArch, - UnimplementedOs, - RegisterContextRequired, - ThreadContextNotSupported, -}; - -/// Returns a slice containing the backing storage for `reg_number`. -/// -/// This function assumes the Dwarf information corresponds not necessarily to -/// the current executable, but at least with a matching CPU architecture and -/// OS. It is planned to lift this limitation with a future enhancement. -/// -/// `reg_context` describes in what context the register number is used, as it can have different -/// meanings depending on the DWARF container. It is only required when getting the stack or -/// frame pointer register on some architectures. -pub fn regBytes( - thread_context_ptr: *std.debug.ThreadContext, - reg_number: u8, - reg_context: ?RegisterContext, -) RegBytesError![]u8 { - if (builtin.os.tag == .windows) { - return switch (builtin.cpu.arch) { - .x86 => switch (reg_number) { - 0 => mem.asBytes(&thread_context_ptr.Eax), - 1 => mem.asBytes(&thread_context_ptr.Ecx), - 2 => mem.asBytes(&thread_context_ptr.Edx), - 3 => mem.asBytes(&thread_context_ptr.Ebx), - 4 => mem.asBytes(&thread_context_ptr.Esp), - 5 => mem.asBytes(&thread_context_ptr.Ebp), - 6 => mem.asBytes(&thread_context_ptr.Esi), - 7 => mem.asBytes(&thread_context_ptr.Edi), - 8 => mem.asBytes(&thread_context_ptr.Eip), - 9 => mem.asBytes(&thread_context_ptr.EFlags), - 10 => mem.asBytes(&thread_context_ptr.SegCs), - 11 => mem.asBytes(&thread_context_ptr.SegSs), - 12 => mem.asBytes(&thread_context_ptr.SegDs), - 13 => mem.asBytes(&thread_context_ptr.SegEs), - 14 => mem.asBytes(&thread_context_ptr.SegFs), - 15 => mem.asBytes(&thread_context_ptr.SegGs), - else => error.InvalidRegister, - }, - .x86_64 => switch (reg_number) { - 0 => mem.asBytes(&thread_context_ptr.Rax), - 1 => mem.asBytes(&thread_context_ptr.Rdx), - 2 => mem.asBytes(&thread_context_ptr.Rcx), - 3 => mem.asBytes(&thread_context_ptr.Rbx), - 4 => mem.asBytes(&thread_context_ptr.Rsi), - 5 => mem.asBytes(&thread_context_ptr.Rdi), - 6 => mem.asBytes(&thread_context_ptr.Rbp), - 7 => mem.asBytes(&thread_context_ptr.Rsp), - 8 => mem.asBytes(&thread_context_ptr.R8), - 9 => mem.asBytes(&thread_context_ptr.R9), - 10 => mem.asBytes(&thread_context_ptr.R10), - 11 => mem.asBytes(&thread_context_ptr.R11), - 12 => mem.asBytes(&thread_context_ptr.R12), - 13 => mem.asBytes(&thread_context_ptr.R13), - 14 => mem.asBytes(&thread_context_ptr.R14), - 15 => mem.asBytes(&thread_context_ptr.R15), - 16 => mem.asBytes(&thread_context_ptr.Rip), - else => error.InvalidRegister, - }, - .aarch64, .aarch64_be => switch (reg_number) { - 0...30 => mem.asBytes(&thread_context_ptr.DUMMYUNIONNAME.X[reg_number]), - 31 => mem.asBytes(&thread_context_ptr.Sp), - 32 => mem.asBytes(&thread_context_ptr.Pc), - else => error.InvalidRegister, - }, - else => error.UnimplementedArch, - }; - } - - if (!std.debug.have_ucontext) return error.ThreadContextNotSupported; - - const ucontext_ptr = thread_context_ptr; - return switch (builtin.cpu.arch) { - .x86 => switch (builtin.os.tag) { - .linux, .netbsd, .solaris, .illumos => switch (reg_number) { - 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EAX]), - 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ECX]), - 2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EDX]), - 3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EBX]), - 4...5 => if (reg_context) |r| bytes: { - if (reg_number == 4) { - break :bytes if (r.eh_frame and r.is_macho) - mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EBP]) - else - mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ESP]); - } else { - break :bytes if (r.eh_frame and r.is_macho) - mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ESP]) - else - mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EBP]); - } - } else error.RegisterContextRequired, - 6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ESI]), - 7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EDI]), - 8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EIP]), - 9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EFL]), - 10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.CS]), - 11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.SS]), - 12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.DS]), - 13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ES]), - 14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.FS]), - 15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.GS]), - 16...23 => error.InvalidRegister, // TODO: Support loading ST0-ST7 from mcontext.fpregs - 32...39 => error.InvalidRegister, // TODO: Support loading XMM0-XMM7 from mcontext.fpregs - else => error.InvalidRegister, - }, - else => error.UnimplementedOs, - }, - .x86_64 => switch (builtin.os.tag) { - .linux, .solaris, .illumos => switch (reg_number) { - 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RAX]), - 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RDX]), - 2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RCX]), - 3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RBX]), - 4 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RSI]), - 5 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RDI]), - 6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RBP]), - 7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RSP]), - 8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R8]), - 9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R9]), - 10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R10]), - 11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R11]), - 12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R12]), - 13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R13]), - 14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R14]), - 15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R15]), - 16 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RIP]), - 17...32 => |i| if (builtin.os.tag.isSolarish()) - mem.asBytes(&ucontext_ptr.mcontext.fpregs.chip_state.xmm[i - 17]) - else - mem.asBytes(&ucontext_ptr.mcontext.fpregs.xmm[i - 17]), - else => error.InvalidRegister, - }, - .freebsd => switch (reg_number) { - 0 => mem.asBytes(&ucontext_ptr.mcontext.rax), - 1 => mem.asBytes(&ucontext_ptr.mcontext.rdx), - 2 => mem.asBytes(&ucontext_ptr.mcontext.rcx), - 3 => mem.asBytes(&ucontext_ptr.mcontext.rbx), - 4 => mem.asBytes(&ucontext_ptr.mcontext.rsi), - 5 => mem.asBytes(&ucontext_ptr.mcontext.rdi), - 6 => mem.asBytes(&ucontext_ptr.mcontext.rbp), - 7 => mem.asBytes(&ucontext_ptr.mcontext.rsp), - 8 => mem.asBytes(&ucontext_ptr.mcontext.r8), - 9 => mem.asBytes(&ucontext_ptr.mcontext.r9), - 10 => mem.asBytes(&ucontext_ptr.mcontext.r10), - 11 => mem.asBytes(&ucontext_ptr.mcontext.r11), - 12 => mem.asBytes(&ucontext_ptr.mcontext.r12), - 13 => mem.asBytes(&ucontext_ptr.mcontext.r13), - 14 => mem.asBytes(&ucontext_ptr.mcontext.r14), - 15 => mem.asBytes(&ucontext_ptr.mcontext.r15), - 16 => mem.asBytes(&ucontext_ptr.mcontext.rip), - // TODO: Extract xmm state from mcontext.fpstate? - else => error.InvalidRegister, - }, - .openbsd => switch (reg_number) { - 0 => mem.asBytes(&ucontext_ptr.sc_rax), - 1 => mem.asBytes(&ucontext_ptr.sc_rdx), - 2 => mem.asBytes(&ucontext_ptr.sc_rcx), - 3 => mem.asBytes(&ucontext_ptr.sc_rbx), - 4 => mem.asBytes(&ucontext_ptr.sc_rsi), - 5 => mem.asBytes(&ucontext_ptr.sc_rdi), - 6 => mem.asBytes(&ucontext_ptr.sc_rbp), - 7 => mem.asBytes(&ucontext_ptr.sc_rsp), - 8 => mem.asBytes(&ucontext_ptr.sc_r8), - 9 => mem.asBytes(&ucontext_ptr.sc_r9), - 10 => mem.asBytes(&ucontext_ptr.sc_r10), - 11 => mem.asBytes(&ucontext_ptr.sc_r11), - 12 => mem.asBytes(&ucontext_ptr.sc_r12), - 13 => mem.asBytes(&ucontext_ptr.sc_r13), - 14 => mem.asBytes(&ucontext_ptr.sc_r14), - 15 => mem.asBytes(&ucontext_ptr.sc_r15), - 16 => mem.asBytes(&ucontext_ptr.sc_rip), - // TODO: Extract xmm state from sc_fpstate? - else => error.InvalidRegister, - }, - .macos, .ios => switch (reg_number) { - 0 => mem.asBytes(&ucontext_ptr.mcontext.ss.rax), - 1 => mem.asBytes(&ucontext_ptr.mcontext.ss.rdx), - 2 => mem.asBytes(&ucontext_ptr.mcontext.ss.rcx), - 3 => mem.asBytes(&ucontext_ptr.mcontext.ss.rbx), - 4 => mem.asBytes(&ucontext_ptr.mcontext.ss.rsi), - 5 => mem.asBytes(&ucontext_ptr.mcontext.ss.rdi), - 6 => mem.asBytes(&ucontext_ptr.mcontext.ss.rbp), - 7 => mem.asBytes(&ucontext_ptr.mcontext.ss.rsp), - 8 => mem.asBytes(&ucontext_ptr.mcontext.ss.r8), - 9 => mem.asBytes(&ucontext_ptr.mcontext.ss.r9), - 10 => mem.asBytes(&ucontext_ptr.mcontext.ss.r10), - 11 => mem.asBytes(&ucontext_ptr.mcontext.ss.r11), - 12 => mem.asBytes(&ucontext_ptr.mcontext.ss.r12), - 13 => mem.asBytes(&ucontext_ptr.mcontext.ss.r13), - 14 => mem.asBytes(&ucontext_ptr.mcontext.ss.r14), - 15 => mem.asBytes(&ucontext_ptr.mcontext.ss.r15), - 16 => mem.asBytes(&ucontext_ptr.mcontext.ss.rip), - else => error.InvalidRegister, - }, - else => error.UnimplementedOs, - }, - .arm, .armeb, .thumb, .thumbeb => switch (builtin.os.tag) { - .linux => switch (reg_number) { - 0 => mem.asBytes(&ucontext_ptr.mcontext.arm_r0), - 1 => mem.asBytes(&ucontext_ptr.mcontext.arm_r1), - 2 => mem.asBytes(&ucontext_ptr.mcontext.arm_r2), - 3 => mem.asBytes(&ucontext_ptr.mcontext.arm_r3), - 4 => mem.asBytes(&ucontext_ptr.mcontext.arm_r4), - 5 => mem.asBytes(&ucontext_ptr.mcontext.arm_r5), - 6 => mem.asBytes(&ucontext_ptr.mcontext.arm_r6), - 7 => mem.asBytes(&ucontext_ptr.mcontext.arm_r7), - 8 => mem.asBytes(&ucontext_ptr.mcontext.arm_r8), - 9 => mem.asBytes(&ucontext_ptr.mcontext.arm_r9), - 10 => mem.asBytes(&ucontext_ptr.mcontext.arm_r10), - 11 => mem.asBytes(&ucontext_ptr.mcontext.arm_fp), - 12 => mem.asBytes(&ucontext_ptr.mcontext.arm_ip), - 13 => mem.asBytes(&ucontext_ptr.mcontext.arm_sp), - 14 => mem.asBytes(&ucontext_ptr.mcontext.arm_lr), - 15 => mem.asBytes(&ucontext_ptr.mcontext.arm_pc), - // CPSR is not allocated a register number (See: https://github.com/ARM-software/abi-aa/blob/main/aadwarf32/aadwarf32.rst, Section 4.1) - else => error.InvalidRegister, - }, - else => error.UnimplementedOs, - }, - .aarch64, .aarch64_be => switch (builtin.os.tag) { - .macos, .ios, .watchos => switch (reg_number) { - 0...28 => mem.asBytes(&ucontext_ptr.mcontext.ss.regs[reg_number]), - 29 => mem.asBytes(&ucontext_ptr.mcontext.ss.fp), - 30 => mem.asBytes(&ucontext_ptr.mcontext.ss.lr), - 31 => mem.asBytes(&ucontext_ptr.mcontext.ss.sp), - 32 => mem.asBytes(&ucontext_ptr.mcontext.ss.pc), - - // TODO: Find storage for this state - //34 => mem.asBytes(&ucontext_ptr.ra_sign_state), - - // V0-V31 - 64...95 => mem.asBytes(&ucontext_ptr.mcontext.ns.q[reg_number - 64]), - else => error.InvalidRegister, - }, - .netbsd => switch (reg_number) { - 0...34 => mem.asBytes(&ucontext_ptr.mcontext.gregs[reg_number]), - else => error.InvalidRegister, - }, - .freebsd => switch (reg_number) { - 0...29 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.x[reg_number]), - 30 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.lr), - 31 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.sp), - - // TODO: This seems wrong, but it was in the previous debug.zig code for mapping PC, check this - 32 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.elr), - - else => error.InvalidRegister, - }, - .openbsd => switch (reg_number) { - 0...30 => mem.asBytes(&ucontext_ptr.sc_x[reg_number]), - 31 => mem.asBytes(&ucontext_ptr.sc_sp), - 32 => mem.asBytes(&ucontext_ptr.sc_lr), - 33 => mem.asBytes(&ucontext_ptr.sc_elr), - 34 => mem.asBytes(&ucontext_ptr.sc_spsr), - else => error.InvalidRegister, - }, - else => switch (reg_number) { - 0...30 => mem.asBytes(&ucontext_ptr.mcontext.regs[reg_number]), - 31 => mem.asBytes(&ucontext_ptr.mcontext.sp), - 32 => mem.asBytes(&ucontext_ptr.mcontext.pc), - else => error.InvalidRegister, - }, - }, - else => error.UnimplementedArch, - }; -} - -/// Returns a pointer to a register stored in a ThreadContext, preserving the -/// pointer attributes of the context. -pub fn regValueNative( - thread_context_ptr: *std.debug.ThreadContext, - reg_number: u8, - reg_context: ?RegisterContext, -) !*align(1) usize { - const reg_bytes = try regBytes(thread_context_ptr, reg_number, reg_context); - if (@sizeOf(usize) != reg_bytes.len) return error.IncompatibleRegisterSize; - return mem.bytesAsValue(usize, reg_bytes[0..@sizeOf(usize)]); -} diff --git a/lib/std/debug/Dwarf/call_frame.zig b/lib/std/debug/Dwarf/call_frame.zig deleted file mode 100644 index f78ed4378b..0000000000 --- a/lib/std/debug/Dwarf/call_frame.zig +++ /dev/null @@ -1,292 +0,0 @@ -const builtin = @import("builtin"); -const std = @import("../../std.zig"); -const mem = std.mem; -const debug = std.debug; -const leb = std.leb; -const DW = std.dwarf; -const abi = std.debug.Dwarf.abi; -const assert = std.debug.assert; -const native_endian = builtin.cpu.arch.endian(); - -/// TODO merge with std.dwarf.CFA -const Opcode = enum(u8) { - advance_loc = 0x1 << 6, - offset = 0x2 << 6, - restore = 0x3 << 6, - - nop = 0x00, - set_loc = 0x01, - advance_loc1 = 0x02, - advance_loc2 = 0x03, - advance_loc4 = 0x04, - offset_extended = 0x05, - restore_extended = 0x06, - undefined = 0x07, - same_value = 0x08, - register = 0x09, - remember_state = 0x0a, - restore_state = 0x0b, - def_cfa = 0x0c, - def_cfa_register = 0x0d, - def_cfa_offset = 0x0e, - def_cfa_expression = 0x0f, - expression = 0x10, - offset_extended_sf = 0x11, - def_cfa_sf = 0x12, - def_cfa_offset_sf = 0x13, - val_offset = 0x14, - val_offset_sf = 0x15, - val_expression = 0x16, - - // These opcodes encode an operand in the lower 6 bits of the opcode itself - pub const lo_inline = @intFromEnum(Opcode.advance_loc); - pub const hi_inline = @intFromEnum(Opcode.restore) | 0b111111; - - // These opcodes are trailed by zero or more operands - pub const lo_reserved = @intFromEnum(Opcode.nop); - pub const hi_reserved = @intFromEnum(Opcode.val_expression); - - // Vendor-specific opcodes - pub const lo_user = 0x1c; - pub const hi_user = 0x3f; -}; - -fn readBlock(reader: *std.Io.Reader) ![]const u8 { - const block_len = try reader.takeLeb128(usize); - return reader.take(block_len); -} - -pub const Instruction = union(Opcode) { - advance_loc: struct { - delta: u8, - }, - offset: struct { - register: u8, - offset: u64, - }, - restore: struct { - register: u8, - }, - nop: void, - set_loc: struct { - address: u64, - }, - advance_loc1: struct { - delta: u8, - }, - advance_loc2: struct { - delta: u16, - }, - advance_loc4: struct { - delta: u32, - }, - offset_extended: struct { - register: u8, - offset: u64, - }, - restore_extended: struct { - register: u8, - }, - undefined: struct { - register: u8, - }, - same_value: struct { - register: u8, - }, - register: struct { - register: u8, - target_register: u8, - }, - remember_state: void, - restore_state: void, - def_cfa: struct { - register: u8, - offset: u64, - }, - def_cfa_register: struct { - register: u8, - }, - def_cfa_offset: struct { - offset: u64, - }, - def_cfa_expression: struct { - block: []const u8, - }, - expression: struct { - register: u8, - block: []const u8, - }, - offset_extended_sf: struct { - register: u8, - offset: i64, - }, - def_cfa_sf: struct { - register: u8, - offset: i64, - }, - def_cfa_offset_sf: struct { - offset: i64, - }, - val_offset: struct { - register: u8, - offset: u64, - }, - val_offset_sf: struct { - register: u8, - offset: i64, - }, - val_expression: struct { - register: u8, - block: []const u8, - }, - - pub fn read( - reader: *std.Io.Reader, - addr_size_bytes: u8, - endian: std.builtin.Endian, - ) !Instruction { - switch (try reader.takeByte()) { - Opcode.lo_inline...Opcode.hi_inline => |opcode| { - const e: Opcode = @enumFromInt(opcode & 0b11000000); - const value: u6 = @intCast(opcode & 0b111111); - return switch (e) { - .advance_loc => .{ - .advance_loc = .{ .delta = value }, - }, - .offset => .{ - .offset = .{ - .register = value, - .offset = try reader.takeLeb128(u64), - }, - }, - .restore => .{ - .restore = .{ .register = value }, - }, - else => unreachable, - }; - }, - Opcode.lo_reserved...Opcode.hi_reserved => |opcode| { - const e: Opcode = @enumFromInt(opcode); - return switch (e) { - .advance_loc, - .offset, - .restore, - => unreachable, - .nop => .{ .nop = {} }, - .set_loc => .{ - .set_loc = .{ - .address = switch (addr_size_bytes) { - 2 => try reader.takeInt(u16, endian), - 4 => try reader.takeInt(u32, endian), - 8 => try reader.takeInt(u64, endian), - else => return error.InvalidAddrSize, - }, - }, - }, - .advance_loc1 => .{ - .advance_loc1 = .{ .delta = try reader.takeByte() }, - }, - .advance_loc2 => .{ - .advance_loc2 = .{ .delta = try reader.takeInt(u16, endian) }, - }, - .advance_loc4 => .{ - .advance_loc4 = .{ .delta = try reader.takeInt(u32, endian) }, - }, - .offset_extended => .{ - .offset_extended = .{ - .register = try reader.takeLeb128(u8), - .offset = try reader.takeLeb128(u64), - }, - }, - .restore_extended => .{ - .restore_extended = .{ - .register = try reader.takeLeb128(u8), - }, - }, - .undefined => .{ - .undefined = .{ - .register = try reader.takeLeb128(u8), - }, - }, - .same_value => .{ - .same_value = .{ - .register = try reader.takeLeb128(u8), - }, - }, - .register => .{ - .register = .{ - .register = try reader.takeLeb128(u8), - .target_register = try reader.takeLeb128(u8), - }, - }, - .remember_state => .{ .remember_state = {} }, - .restore_state => .{ .restore_state = {} }, - .def_cfa => .{ - .def_cfa = .{ - .register = try reader.takeLeb128(u8), - .offset = try reader.takeLeb128(u64), - }, - }, - .def_cfa_register => .{ - .def_cfa_register = .{ - .register = try reader.takeLeb128(u8), - }, - }, - .def_cfa_offset => .{ - .def_cfa_offset = .{ - .offset = try reader.takeLeb128(u64), - }, - }, - .def_cfa_expression => .{ - .def_cfa_expression = .{ - .block = try readBlock(reader), - }, - }, - .expression => .{ - .expression = .{ - .register = try reader.takeLeb128(u8), - .block = try readBlock(reader), - }, - }, - .offset_extended_sf => .{ - .offset_extended_sf = .{ - .register = try reader.takeLeb128(u8), - .offset = try reader.takeLeb128(i64), - }, - }, - .def_cfa_sf => .{ - .def_cfa_sf = .{ - .register = try reader.takeLeb128(u8), - .offset = try reader.takeLeb128(i64), - }, - }, - .def_cfa_offset_sf => .{ - .def_cfa_offset_sf = .{ - .offset = try reader.takeLeb128(i64), - }, - }, - .val_offset => .{ - .val_offset = .{ - .register = try reader.takeLeb128(u8), - .offset = try reader.takeLeb128(u64), - }, - }, - .val_offset_sf => .{ - .val_offset_sf = .{ - .register = try reader.takeLeb128(u8), - .offset = try reader.takeLeb128(i64), - }, - }, - .val_expression => .{ - .val_expression = .{ - .register = try reader.takeLeb128(u8), - .block = try readBlock(reader), - }, - }, - }; - }, - Opcode.lo_user...Opcode.hi_user => return error.UnimplementedUserOpcode, - else => return error.InvalidOpcode, - } - } -}; diff --git a/lib/std/debug/Dwarf/expression.zig b/lib/std/debug/Dwarf/expression.zig index 68b49587c2..62659ec089 100644 --- a/lib/std/debug/Dwarf/expression.zig +++ b/lib/std/debug/Dwarf/expression.zig @@ -5,12 +5,17 @@ const native_endian = native_arch.endian(); const std = @import("std"); const leb = std.leb; const OP = std.dwarf.OP; -const abi = std.debug.Dwarf.abi; const mem = std.mem; const assert = std.debug.assert; const testing = std.testing; const Writer = std.Io.Writer; +const regNative = std.debug.Dwarf.SelfUnwinder.regNative; + +const ip_reg_num = std.debug.Dwarf.ipRegNum(native_arch).?; +const fp_reg_num = std.debug.Dwarf.fpRegNum(native_arch); +const sp_reg_num = std.debug.Dwarf.spRegNum(native_arch); + /// Expressions can be evaluated in different contexts, each requiring its own set of inputs. /// Callers should specify all the fields relevant to their context. If a field is required /// by the expression and it isn't in the context, error.IncompleteExpressionContext is returned. @@ -23,9 +28,7 @@ pub const Context = struct { object_address: ?*const anyopaque = null, /// .debug_addr section debug_addr: ?[]const u8 = null, - /// Thread context - thread_context: ?*std.debug.ThreadContext = null, - reg_context: ?abi.RegisterContext = null, + cpu_context: ?*std.debug.cpu_context.Native = null, /// Call frame address, if in a CFI context cfa: ?usize = null, /// This expression is a sub-expression from an OP.entry_value instruction @@ -62,7 +65,9 @@ pub const Error = error{ InvalidTypeLength, TruncatedIntegralType, -} || abi.RegBytesError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero, ReadFailed }; + + IncompatibleRegisterSize, +} || std.debug.cpu_context.DwarfRegisterError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero, ReadFailed }; /// A stack machine that can decode and run DWARF expressions. /// Expressions can be decoded for non-native address size and endianness, @@ -369,29 +374,20 @@ pub fn StackMachine(comptime options: Options) type { OP.breg0...OP.breg31, OP.bregx, => { - if (context.thread_context == null) return error.IncompleteExpressionContext; - - const base_register = operand.?.base_register; - var value: i64 = @intCast(mem.readInt(usize, (try abi.regBytes( - context.thread_context.?, - base_register.base_register, - context.reg_context, - ))[0..@sizeOf(usize)], native_endian)); - value += base_register.offset; - try self.stack.append(allocator, .{ .generic = @intCast(value) }); + const cpu_context = context.cpu_context orelse return error.IncompleteExpressionContext; + + const br = operand.?.base_register; + const value: i64 = @intCast((try regNative(cpu_context, br.base_register)).*); + try self.stack.append(allocator, .{ .generic = @intCast(value + br.offset) }); }, OP.regval_type => { - const register_type = operand.?.register_type; - const value = mem.readInt(usize, (try abi.regBytes( - context.thread_context.?, - register_type.register, - context.reg_context, - ))[0..@sizeOf(usize)], native_endian); + const cpu_context = context.cpu_context orelse return error.IncompleteExpressionContext; + const rt = operand.?.register_type; try self.stack.append(allocator, .{ .regval_type = .{ - .type_offset = register_type.type_offset, + .type_offset = rt.type_offset, .type_size = @sizeOf(addr_type), - .value = value, + .value = (try regNative(cpu_context, rt.register)).*, }, }); }, @@ -734,14 +730,14 @@ pub fn StackMachine(comptime options: Options) type { // TODO: The spec states that this sub-expression needs to observe the state (ie. registers) // as it was upon entering the current subprogram. If this isn't being called at the - // end of a frame unwind operation, an additional ThreadContext with this state will be needed. + // end of a frame unwind operation, an additional cpu_context.Native with this state will be needed. if (isOpcodeRegisterLocation(block[0])) { - if (context.thread_context == null) return error.IncompleteExpressionContext; + const cpu_context = context.cpu_context orelse return error.IncompleteExpressionContext; var block_stream: std.Io.Reader = .fixed(block); const register = (try readOperand(&block_stream, block[0], context)).?.register; - const value = mem.readInt(usize, (try abi.regBytes(context.thread_context.?, register, context.reg_context))[0..@sizeOf(usize)], native_endian); + const value = (try regNative(cpu_context, register)).*; try self.stack.append(allocator, .{ .generic = value }); } else { var stack_machine: Self = .{}; @@ -1149,55 +1145,36 @@ test "basics" { } // Register values - if (@sizeOf(std.debug.ThreadContext) != 0) { + if (std.debug.cpu_context.Native != noreturn) { stack_machine.reset(); program.clearRetainingCapacity(); - const reg_context = abi.RegisterContext{ - .eh_frame = true, - .is_macho = builtin.os.tag == .macos, - }; - var thread_context: std.debug.ThreadContext = undefined; - std.debug.relocateContext(&thread_context); + var cpu_context: std.debug.cpu_context.Native = undefined; const context = Context{ - .thread_context = &thread_context, - .reg_context = reg_context, + .cpu_context = &cpu_context, }; - // Only test register operations on arch / os that have them implemented - if (abi.regBytes(&thread_context, 0, reg_context)) |reg_bytes| { - - // TODO: Test fbreg (once implemented): mock a DIE and point compile_unit.frame_base at it - - mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian); - (try abi.regValueNative(&thread_context, abi.fpRegNum(native_arch, reg_context), reg_context)).* = 1; - (try abi.regValueNative(&thread_context, abi.spRegNum(native_arch, reg_context), reg_context)).* = 2; - (try abi.regValueNative(&thread_context, abi.ipRegNum(native_arch).?, reg_context)).* = 3; - - try b.writeBreg(writer, abi.fpRegNum(native_arch, reg_context), @as(usize, 100)); - try b.writeBreg(writer, abi.spRegNum(native_arch, reg_context), @as(usize, 200)); - try b.writeBregx(writer, abi.ipRegNum(native_arch).?, @as(usize, 300)); - try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400)); - - _ = try stack_machine.run(program.written(), allocator, context, 0); - - const regval_type = stack_machine.stack.pop().?.regval_type; - try testing.expectEqual(@as(usize, 400), regval_type.type_offset); - try testing.expectEqual(@as(u8, @sizeOf(usize)), regval_type.type_size); - try testing.expectEqual(@as(usize, 0xee), regval_type.value); - - try testing.expectEqual(@as(usize, 303), stack_machine.stack.pop().?.generic); - try testing.expectEqual(@as(usize, 202), stack_machine.stack.pop().?.generic); - try testing.expectEqual(@as(usize, 101), stack_machine.stack.pop().?.generic); - } else |err| { - switch (err) { - error.UnimplementedArch, - error.UnimplementedOs, - error.ThreadContextNotSupported, - => {}, - else => return err, - } - } + const reg_bytes = try cpu_context.dwarfRegisterBytes(0); + + // TODO: Test fbreg (once implemented): mock a DIE and point compile_unit.frame_base at it + + mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian); + (try regNative(&cpu_context, fp_reg_num)).* = 1; + (try regNative(&cpu_context, ip_reg_num)).* = 2; + + try b.writeBreg(writer, fp_reg_num, @as(usize, 100)); + try b.writeBregx(writer, ip_reg_num, @as(usize, 200)); + try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 300)); + + _ = try stack_machine.run(program.written(), allocator, context, 0); + + const regval_type = stack_machine.stack.pop().?.regval_type; + try testing.expectEqual(@as(usize, 300), regval_type.type_offset); + try testing.expectEqual(@as(u8, @sizeOf(usize)), regval_type.type_size); + try testing.expectEqual(@as(usize, 0xee), regval_type.value); + + try testing.expectEqual(@as(usize, 202), stack_machine.stack.pop().?.generic); + try testing.expectEqual(@as(usize, 101), stack_machine.stack.pop().?.generic); } // Stack operations @@ -1585,38 +1562,21 @@ test "basics" { } // Register location description - const reg_context = abi.RegisterContext{ - .eh_frame = true, - .is_macho = builtin.os.tag == .macos, - }; - var thread_context: std.debug.ThreadContext = undefined; - std.debug.relocateContext(&thread_context); - context = Context{ - .thread_context = &thread_context, - .reg_context = reg_context, - }; + var cpu_context: std.debug.cpu_context.Native = undefined; + context = .{ .cpu_context = &cpu_context }; - if (abi.regBytes(&thread_context, 0, reg_context)) |reg_bytes| { - mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian); + const reg_bytes = try cpu_context.dwarfRegisterBytes(0); + mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian); - var sub_program: std.Io.Writer.Allocating = .init(allocator); - defer sub_program.deinit(); - const sub_writer = &sub_program.writer; - try b.writeReg(sub_writer, 0); + var sub_program: std.Io.Writer.Allocating = .init(allocator); + defer sub_program.deinit(); + const sub_writer = &sub_program.writer; + try b.writeReg(sub_writer, 0); - stack_machine.reset(); - program.clearRetainingCapacity(); - try b.writeEntryValue(writer, sub_program.written()); - _ = try stack_machine.run(program.written(), allocator, context, null); - try testing.expectEqual(@as(usize, 0xee), stack_machine.stack.pop().?.generic); - } else |err| { - switch (err) { - error.UnimplementedArch, - error.UnimplementedOs, - error.ThreadContextNotSupported, - => {}, - else => return err, - } - } + stack_machine.reset(); + program.clearRetainingCapacity(); + try b.writeEntryValue(writer, sub_program.written()); + _ = try stack_machine.run(program.written(), allocator, context, null); + try testing.expectEqual(@as(usize, 0xee), stack_machine.stack.pop().?.generic); } } diff --git a/lib/std/debug/ElfFile.zig b/lib/std/debug/ElfFile.zig new file mode 100644 index 0000000000..5be5ee55c5 --- /dev/null +++ b/lib/std/debug/ElfFile.zig @@ -0,0 +1,536 @@ +//! A helper type for loading an ELF file and collecting its DWARF debug information, unwind +//! information, and symbol table. + +is_64: bool, +endian: Endian, + +/// This is `null` iff any of the required DWARF sections were missing. `ElfFile.load` does *not* +/// call `Dwarf.open`, `Dwarf.scanAllFunctions`, etc; that is the caller's responsibility. +dwarf: ?Dwarf, + +/// If non-`null`, describes the `.eh_frame` section, which can be used with `Dwarf.Unwind`. +eh_frame: ?UnwindSection, +/// If non-`null`, describes the `.debug_frame` section, which can be used with `Dwarf.Unwind`. +debug_frame: ?UnwindSection, + +/// If non-`null`, this is the contents of the `.strtab` section. +strtab: ?[]const u8, +/// If non-`null`, describes the `.symtab` section. +symtab: ?SymtabSection, + +/// Binary search table lazily populated by `searchSymtab`. +symbol_search_table: ?[]usize, + +/// The memory-mapped ELF file, which is referenced by `dwarf`. This field is here only so that +/// this memory can be unmapped by `ElfFile.deinit`. +mapped_file: []align(std.heap.page_size_min) const u8, +/// Sometimes, debug info is stored separately to the main ELF file. In that case, `mapped_file` +/// is the mapped ELF binary, and `mapped_debug_file` is the mapped debug info file. Both must +/// be unmapped by `ElfFile.deinit`. +mapped_debug_file: ?[]align(std.heap.page_size_min) const u8, + +arena: std.heap.ArenaAllocator.State, + +pub const UnwindSection = struct { + vaddr: u64, + bytes: []const u8, +}; +pub const SymtabSection = struct { + entry_size: u64, + bytes: []const u8, +}; + +pub const DebugInfoSearchPaths = struct { + /// The location of a debuginfod client directory, which acts as a search path for build IDs. If + /// given, we can load from this directory opportunistically, but make no effort to populate it. + /// To avoid allocation when building the search paths, this is given as two components which + /// will be concatenated. + debuginfod_client: ?[2][]const u8, + /// All "global debug directories" on the system. These are used as search paths for both debug + /// links and build IDs. On typical systems this is just "/usr/lib/debug". + global_debug: []const []const u8, + /// The path to the dirname of the ELF file, which acts as a search path for debug links. + exe_dir: ?[]const u8, + + pub const none: DebugInfoSearchPaths = .{ + .debuginfod_client = null, + .global_debug = &.{}, + .exe_dir = null, + }; + + pub fn native(exe_path: []const u8) DebugInfoSearchPaths { + return .{ + .debuginfod_client = p: { + if (std.posix.getenv("DEBUGINFOD_CACHE_PATH")) |p| { + break :p .{ p, "" }; + } + if (std.posix.getenv("XDG_CACHE_HOME")) |cache_path| { + break :p .{ cache_path, "/debuginfod_client" }; + } + if (std.posix.getenv("HOME")) |home_path| { + break :p .{ home_path, "/.cache/debuginfod_client" }; + } + break :p null; + }, + .global_debug = &.{ + "/usr/lib/debug", + }, + .exe_dir = std.fs.path.dirname(exe_path) orelse ".", + }; + } +}; + +pub fn deinit(ef: *ElfFile, gpa: Allocator) void { + if (ef.dwarf) |*dwarf| dwarf.deinit(gpa); + if (ef.symbol_search_table) |t| gpa.free(t); + var arena = ef.arena.promote(gpa); + arena.deinit(); + + std.posix.munmap(ef.mapped_file); + if (ef.mapped_debug_file) |m| std.posix.munmap(m); + + ef.* = undefined; +} + +pub const LoadError = error{ + OutOfMemory, + Overflow, + TruncatedElfFile, + InvalidCompressedSection, + InvalidElfMagic, + InvalidElfVersion, + InvalidElfClass, + InvalidElfEndian, + // The remaining errors all occur when attemping to stat or mmap a file. + SystemResources, + MemoryMappingNotSupported, + AccessDenied, + LockedMemoryLimitExceeded, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + Unexpected, +}; + +pub fn load( + gpa: Allocator, + elf_file: std.fs.File, + opt_build_id: ?[]const u8, + di_search_paths: *const DebugInfoSearchPaths, +) LoadError!ElfFile { + var arena_instance: std.heap.ArenaAllocator = .init(gpa); + errdefer arena_instance.deinit(); + const arena = arena_instance.allocator(); + + var result = loadInner(arena, elf_file, null) catch |err| switch (err) { + error.CrcMismatch => unreachable, // we passed crc as null + else => |e| return e, + }; + errdefer std.posix.munmap(result.mapped_mem); + + // `loadInner` did most of the work, but we might need to load an external debug info file + + const di_mapped_mem: ?[]align(std.heap.page_size_min) const u8 = load_di: { + if (result.sections.get(.debug_info) != null and + result.sections.get(.debug_abbrev) != null and + result.sections.get(.debug_str) != null and + result.sections.get(.debug_line) != null) + { + // The info is already loaded from this file alone! + break :load_di null; + } + + // We're missing some debug info---let's try and load it from a separate file. + + build_id: { + const build_id = opt_build_id orelse break :build_id; + if (build_id.len < 3) break :build_id; + + for (di_search_paths.global_debug) |global_debug| { + if (try loadSeparateDebugFile(arena, &result, null, "{s}/.build-id/{x}/{x}.debug", .{ + global_debug, + build_id[0..1], + build_id[1..], + })) |mapped| break :load_di mapped; + } + + if (di_search_paths.debuginfod_client) |components| { + if (try loadSeparateDebugFile(arena, &result, null, "{s}{s}/{x}/debuginfo", .{ + components[0], + components[1], + build_id, + })) |mapped| break :load_di mapped; + } + } + + debug_link: { + const section = result.sections.get(.gnu_debuglink) orelse break :debug_link; + const debug_filename = std.mem.sliceTo(section.bytes, 0); + const crc_offset = std.mem.alignForward(usize, debug_filename.len + 1, 4); + if (section.bytes.len < crc_offset + 4) break :debug_link; + const debug_crc = std.mem.readInt(u32, section.bytes[crc_offset..][0..4], result.endian); + + const exe_dir = di_search_paths.exe_dir orelse break :debug_link; + + if (try loadSeparateDebugFile(arena, &result, debug_crc, "{s}/{s}", .{ + exe_dir, + debug_filename, + })) |mapped| break :load_di mapped; + if (try loadSeparateDebugFile(arena, &result, debug_crc, "{s}/.debug/{s}", .{ + exe_dir, + debug_filename, + })) |mapped| break :load_di mapped; + for (di_search_paths.global_debug) |global_debug| { + // This looks like a bug; it isn't. They really do embed the absolute path to the + // exe's dirname, *under* the global debug path. + if (try loadSeparateDebugFile(arena, &result, debug_crc, "{s}/{s}/{s}", .{ + global_debug, + exe_dir, + debug_filename, + })) |mapped| break :load_di mapped; + } + } + + break :load_di null; + }; + errdefer comptime unreachable; + + return .{ + .is_64 = result.is_64, + .endian = result.endian, + .dwarf = dwarf: { + if (result.sections.get(.debug_info) == null or + result.sections.get(.debug_abbrev) == null or + result.sections.get(.debug_str) == null or + result.sections.get(.debug_line) == null) + { + break :dwarf null; // debug info not present + } + var sections: Dwarf.SectionArray = @splat(null); + inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields) |f| { + if (result.sections.get(@field(Section.Id, f.name))) |s| { + sections[f.value] = .{ .data = s.bytes, .owned = false }; + } + } + break :dwarf .{ .sections = sections }; + }, + .eh_frame = if (result.sections.get(.eh_frame)) |s| .{ + .vaddr = s.header.sh_addr, + .bytes = s.bytes, + } else null, + .debug_frame = if (result.sections.get(.debug_frame)) |s| .{ + .vaddr = s.header.sh_addr, + .bytes = s.bytes, + } else null, + .strtab = if (result.sections.get(.strtab)) |s| s.bytes else null, + .symtab = if (result.sections.get(.symtab)) |s| .{ + .entry_size = s.header.sh_entsize, + .bytes = s.bytes, + } else null, + .symbol_search_table = null, + .mapped_file = result.mapped_mem, + .mapped_debug_file = di_mapped_mem, + .arena = arena_instance.state, + }; +} + +pub fn searchSymtab(ef: *ElfFile, gpa: Allocator, vaddr: u64) error{ + NoSymtab, + NoStrtab, + BadSymtab, + OutOfMemory, +}!std.debug.Symbol { + const symtab = ef.symtab orelse return error.NoSymtab; + const strtab = ef.strtab orelse return error.NoStrtab; + + if (symtab.bytes.len % symtab.entry_size != 0) return error.BadSymtab; + + const swap_endian = ef.endian != @import("builtin").cpu.arch.endian(); + + switch (ef.is_64) { + inline true, false => |is_64| { + const Sym = if (is_64) elf.Elf64_Sym else elf.Elf32_Sym; + if (symtab.entry_size != @sizeOf(Sym)) return error.BadSymtab; + const symbols: []align(1) const Sym = @ptrCast(symtab.bytes); + if (ef.symbol_search_table == null) { + ef.symbol_search_table = try buildSymbolSearchTable(gpa, ef.endian, Sym, symbols); + } + const search_table = ef.symbol_search_table.?; + const SearchContext = struct { + swap_endian: bool, + target: u64, + symbols: []align(1) const Sym, + fn predicate(ctx: @This(), sym_index: usize) bool { + // We need to return `true` for the first N items, then `false` for the rest -- + // the index we'll get out is the first `false` one. So, we'll return `true` iff + // the target address is after the *end* of this symbol. This synchronizes with + // the logic in `buildSymbolSearchTable` which sorts by *end* address. + var sym = ctx.symbols[sym_index]; + if (ctx.swap_endian) std.mem.byteSwapAllFields(Sym, &sym); + const sym_end = sym.st_value + sym.st_size; + return ctx.target >= sym_end; + } + }; + const sym_index_index = std.sort.partitionPoint(usize, search_table, @as(SearchContext, .{ + .swap_endian = swap_endian, + .target = vaddr, + .symbols = symbols, + }), SearchContext.predicate); + if (sym_index_index == search_table.len) return .unknown; + var sym = symbols[search_table[sym_index_index]]; + if (swap_endian) std.mem.byteSwapAllFields(Sym, &sym); + if (vaddr < sym.st_value or vaddr >= sym.st_value + sym.st_size) return .unknown; + return .{ + .name = std.mem.sliceTo(strtab[sym.st_name..], 0), + .compile_unit_name = null, + .source_location = null, + }; + }, + } +} + +fn buildSymbolSearchTable(gpa: Allocator, endian: Endian, comptime Sym: type, symbols: []align(1) const Sym) error{ + OutOfMemory, + BadSymtab, +}![]usize { + var result: std.ArrayList(usize) = .empty; + defer result.deinit(gpa); + + const swap_endian = endian != @import("builtin").cpu.arch.endian(); + + for (symbols, 0..) |sym_orig, sym_index| { + var sym = sym_orig; + if (swap_endian) std.mem.byteSwapAllFields(Sym, &sym); + if (sym.st_name == 0) continue; + if (sym.st_shndx == elf.SHN_UNDEF) continue; + try result.append(gpa, sym_index); + } + + const SortContext = struct { + swap_endian: bool, + symbols: []align(1) const Sym, + fn lessThan(ctx: @This(), lhs_sym_index: usize, rhs_sym_index: usize) bool { + // We sort by *end* address, not start address. This matches up with logic in `searchSymtab`. + var lhs_sym = ctx.symbols[lhs_sym_index]; + var rhs_sym = ctx.symbols[rhs_sym_index]; + if (ctx.swap_endian) { + std.mem.byteSwapAllFields(Sym, &lhs_sym); + std.mem.byteSwapAllFields(Sym, &rhs_sym); + } + const lhs_val = lhs_sym.st_value + lhs_sym.st_size; + const rhs_val = rhs_sym.st_value + rhs_sym.st_size; + return lhs_val < rhs_val; + } + }; + std.mem.sort(usize, result.items, @as(SortContext, .{ + .swap_endian = swap_endian, + .symbols = symbols, + }), SortContext.lessThan); + + return result.toOwnedSlice(gpa); +} + +/// Only used locally, during `load`. +const Section = struct { + header: elf.Elf64_Shdr, + bytes: []const u8, + const Id = enum { + // DWARF sections: see `Dwarf.Section.Id`. + debug_info, + debug_abbrev, + debug_str, + debug_str_offsets, + debug_line, + debug_line_str, + debug_ranges, + debug_loclists, + debug_rnglists, + debug_addr, + debug_names, + // Then anything else we're interested in. + gnu_debuglink, + eh_frame, + debug_frame, + symtab, + strtab, + }; + const Array = std.enums.EnumArray(Section.Id, ?Section); +}; + +fn loadSeparateDebugFile(arena: Allocator, main_loaded: *LoadInnerResult, opt_crc: ?u32, comptime fmt: []const u8, args: anytype) Allocator.Error!?[]align(std.heap.page_size_min) const u8 { + const path = try std.fmt.allocPrint(arena, fmt, args); + const elf_file = std.fs.cwd().openFile(path, .{}) catch return null; + defer elf_file.close(); + + const result = loadInner(arena, elf_file, opt_crc) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.CrcMismatch => return null, + else => return null, + }; + errdefer comptime unreachable; + + const have_debug_sections = inline for (@as([]const []const u8, &.{ + "debug_info", + "debug_abbrev", + "debug_str", + "debug_line", + })) |name| { + const s = @field(Section.Id, name); + if (main_loaded.sections.get(s) == null and result.sections.get(s) != null) { + break false; + } + } else true; + + if (result.is_64 != main_loaded.is_64 or + result.endian != main_loaded.endian or + !have_debug_sections) + { + std.posix.munmap(result.mapped_mem); + return null; + } + + inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields) |f| { + const id = @field(Section.Id, f.name); + if (main_loaded.sections.get(id) == null) { + main_loaded.sections.set(id, result.sections.get(id)); + } + } + + return result.mapped_mem; +} + +const LoadInnerResult = struct { + is_64: bool, + endian: Endian, + sections: Section.Array, + mapped_mem: []align(std.heap.page_size_min) const u8, +}; +fn loadInner( + arena: Allocator, + elf_file: std.fs.File, + opt_crc: ?u32, +) (LoadError || error{CrcMismatch})!LoadInnerResult { + const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: { + const file_len = std.math.cast( + usize, + elf_file.getEndPos() catch |err| switch (err) { + error.PermissionDenied => unreachable, // not asking for PROT_EXEC + else => |e| return e, + }, + ) orelse return error.Overflow; + + break :mapped std.posix.mmap( + null, + file_len, + std.posix.PROT.READ, + .{ .TYPE = .SHARED }, + elf_file.handle, + 0, + ) catch |err| switch (err) { + error.MappingAlreadyExists => unreachable, // not using FIXED_NOREPLACE + error.PermissionDenied => unreachable, // not asking for PROT_EXEC + else => |e| return e, + }; + }; + + if (opt_crc) |crc| { + if (std.hash.crc.Crc32.hash(mapped_mem) != crc) { + return error.CrcMismatch; + } + } + errdefer std.posix.munmap(mapped_mem); + + var fr: std.Io.Reader = .fixed(mapped_mem); + + const header = elf.Header.read(&fr) catch |err| switch (err) { + error.ReadFailed => unreachable, + error.EndOfStream => return error.TruncatedElfFile, + + error.InvalidElfMagic, + error.InvalidElfVersion, + error.InvalidElfClass, + error.InvalidElfEndian, + => |e| return e, + }; + const endian = header.endian; + + const shstrtab_shdr_off = try std.math.add( + u64, + header.shoff, + try std.math.mul(u64, header.shstrndx, header.shentsize), + ); + fr.seek = std.math.cast(usize, shstrtab_shdr_off) orelse return error.Overflow; + const shstrtab: []const u8 = if (header.is_64) shstrtab: { + const shdr = fr.takeStruct(elf.Elf64_Shdr, endian) catch return error.TruncatedElfFile; + if (shdr.sh_offset + shdr.sh_size > mapped_mem.len) return error.TruncatedElfFile; + break :shstrtab mapped_mem[@intCast(shdr.sh_offset)..][0..@intCast(shdr.sh_size)]; + } else shstrtab: { + const shdr = fr.takeStruct(elf.Elf32_Shdr, endian) catch return error.TruncatedElfFile; + if (shdr.sh_offset + shdr.sh_size > mapped_mem.len) return error.TruncatedElfFile; + break :shstrtab mapped_mem[@intCast(shdr.sh_offset)..][0..@intCast(shdr.sh_size)]; + }; + + var sections: Section.Array = .initFill(null); + + var it = header.iterateSectionHeadersBuffer(mapped_mem); + while (it.next() catch return error.TruncatedElfFile) |shdr| { + if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue; + if (shdr.sh_name > shstrtab.len) return error.TruncatedElfFile; + const name = std.mem.sliceTo(shstrtab[@intCast(shdr.sh_name)..], 0); + + const section_id: Section.Id = inline for (@typeInfo(Section.Id).@"enum".fields) |s| { + if (std.mem.eql(u8, "." ++ s.name, name)) { + break @enumFromInt(s.value); + } + } else continue; + + if (sections.get(section_id) != null) continue; + + if (shdr.sh_offset + shdr.sh_size > mapped_mem.len) return error.TruncatedElfFile; + const raw_section_bytes = mapped_mem[@intCast(shdr.sh_offset)..][0..@intCast(shdr.sh_size)]; + const section_bytes: []const u8 = bytes: { + if ((shdr.sh_flags & elf.SHF_COMPRESSED) == 0) break :bytes raw_section_bytes; + + var section_reader: std.Io.Reader = .fixed(raw_section_bytes); + const ch_type: elf.COMPRESS, const ch_size: u64 = if (header.is_64) ch: { + const chdr = section_reader.takeStruct(elf.Elf64_Chdr, endian) catch return error.InvalidCompressedSection; + break :ch .{ chdr.ch_type, chdr.ch_size }; + } else ch: { + const chdr = section_reader.takeStruct(elf.Elf32_Chdr, endian) catch return error.InvalidCompressedSection; + break :ch .{ chdr.ch_type, chdr.ch_size }; + }; + if (ch_type != .ZLIB) { + // The compression algorithm is unsupported, but don't make that a hard error; the + // file might still be valid, and we might still be okay without this section. + continue; + } + + const buf = try arena.alloc(u8, std.math.cast(usize, ch_size) orelse return error.Overflow); + var fw: std.Io.Writer = .fixed(buf); + var decompress: std.compress.flate.Decompress = .init(§ion_reader, .zlib, &.{}); + const n = decompress.reader.streamRemaining(&fw) catch |err| switch (err) { + // If a write failed, then `buf` filled up, so `ch_size` was incorrect + error.WriteFailed => return error.InvalidCompressedSection, + // If a read failed, flate expected the section to have more data + error.ReadFailed => return error.InvalidCompressedSection, + }; + // It's also an error if the data is shorter than expected. + if (n != buf.len) return error.InvalidCompressedSection; + break :bytes buf; + }; + sections.set(section_id, .{ .header = shdr, .bytes = section_bytes }); + } + + return .{ + .is_64 = header.is_64, + .endian = endian, + .sections = sections, + .mapped_mem = mapped_mem, + }; +} + +const std = @import("std"); +const Endian = std.builtin.Endian; +const Dwarf = std.debug.Dwarf; +const ElfFile = @This(); +const Allocator = std.mem.Allocator; +const elf = std.elf; diff --git a/lib/std/debug/Info.zig b/lib/std/debug/Info.zig index c809547f73..74119a3ea4 100644 --- a/lib/std/debug/Info.zig +++ b/lib/std/debug/Info.zig @@ -9,7 +9,7 @@ const std = @import("../std.zig"); const Allocator = std.mem.Allocator; const Path = std.Build.Cache.Path; -const Dwarf = std.debug.Dwarf; +const ElfFile = std.debug.ElfFile; const assert = std.debug.assert; const Coverage = std.debug.Coverage; const SourceLocation = std.debug.Coverage.SourceLocation; @@ -17,27 +17,35 @@ const SourceLocation = std.debug.Coverage.SourceLocation; const Info = @This(); /// Sorted by key, ascending. -address_map: std.AutoArrayHashMapUnmanaged(u64, Dwarf.ElfModule), +address_map: std.AutoArrayHashMapUnmanaged(u64, ElfFile), /// Externally managed, outlives this `Info` instance. coverage: *Coverage, -pub const LoadError = Dwarf.ElfModule.LoadError; +pub const LoadError = std.fs.File.OpenError || ElfFile.LoadError || std.debug.Dwarf.ScanError || error{MissingDebugInfo}; pub fn load(gpa: Allocator, path: Path, coverage: *Coverage) LoadError!Info { - var sections: Dwarf.SectionArray = Dwarf.null_section_array; - var elf_module = try Dwarf.ElfModule.loadPath(gpa, path, null, null, §ions, null); - try elf_module.dwarf.populateRanges(gpa); + var file = try path.root_dir.handle.openFile(path.sub_path, .{}); + defer file.close(); + + var elf_file: ElfFile = try .load(gpa, file, null, &.none); + errdefer elf_file.deinit(gpa); + + if (elf_file.dwarf == null) return error.MissingDebugInfo; + try elf_file.dwarf.?.open(gpa, elf_file.endian); + try elf_file.dwarf.?.populateRanges(gpa, elf_file.endian); + var info: Info = .{ .address_map = .{}, .coverage = coverage, }; - try info.address_map.put(gpa, elf_module.base_address, elf_module); + try info.address_map.put(gpa, 0, elf_file); + errdefer comptime unreachable; // elf_file is owned by the map now return info; } pub fn deinit(info: *Info, gpa: Allocator) void { - for (info.address_map.values()) |*elf_module| { - elf_module.dwarf.deinit(gpa); + for (info.address_map.values()) |*elf_file| { + elf_file.dwarf.?.deinit(gpa); } info.address_map.deinit(gpa); info.* = undefined; @@ -57,6 +65,6 @@ pub fn resolveAddresses( ) ResolveAddressesError!void { assert(sorted_pc_addrs.len == output.len); if (info.address_map.entries.len != 1) @panic("TODO"); - const elf_module = &info.address_map.values()[0]; - return info.coverage.resolveAddressesDwarf(gpa, sorted_pc_addrs, output, &elf_module.dwarf); + const elf_file = &info.address_map.values()[0]; + return info.coverage.resolveAddressesDwarf(gpa, elf_file.endian, sorted_pc_addrs, output, &elf_file.dwarf.?); } diff --git a/lib/std/debug/Pdb.zig b/lib/std/debug/Pdb.zig index 008aad6ab6..c10b361f72 100644 --- a/lib/std/debug/Pdb.zig +++ b/lib/std/debug/Pdb.zig @@ -171,6 +171,7 @@ pub fn parseInfoStream(self: *Pdb) !void { const string_table_index = str_tab_index: { const name_bytes_len = try reader.takeInt(u32, .little); const name_bytes = try reader.readAlloc(gpa, name_bytes_len); + defer gpa.free(name_bytes); const HashTableHeader = extern struct { size: u32, @@ -412,8 +413,7 @@ const Msf = struct { return error.InvalidDebugInfo; if (superblock.free_block_map_block != 1 and superblock.free_block_map_block != 2) return error.InvalidDebugInfo; - const file_len = try file_reader.getSize(); - if (superblock.num_blocks * superblock.block_size != file_len) + if (superblock.num_blocks * superblock.block_size != try file_reader.getSize()) return error.InvalidDebugInfo; switch (superblock.block_size) { // llvm only supports 4096 but we can handle any of these values @@ -427,6 +427,7 @@ const Msf = struct { try file_reader.seekTo(superblock.block_size * superblock.block_map_addr); const dir_blocks = try gpa.alloc(u32, dir_block_count); + errdefer gpa.free(dir_blocks); for (dir_blocks) |*b| { b.* = try file_reader.interface.takeInt(u32, .little); } @@ -450,25 +451,25 @@ const Msf = struct { const streams = try gpa.alloc(MsfStream, stream_count); errdefer gpa.free(streams); - for (streams, 0..) |*stream, i| { - const size = stream_sizes[i]; + for (streams, stream_sizes) |*stream, size| { if (size == 0) { stream.* = .empty; - } else { - const blocks = try gpa.alloc(u32, size); - errdefer gpa.free(blocks); - for (blocks) |*block| { - const block_id = try directory.interface.takeInt(u32, .little); - const n = (block_id % superblock.block_size); - // 0 is for pdb.SuperBlock, 1 and 2 for FPMs. - if (block_id == 0 or n == 1 or n == 2 or block_id * superblock.block_size > file_len) - return error.InvalidBlockIndex; - block.* = block_id; - } - const buffer = try gpa.alloc(u8, 64); - errdefer gpa.free(buffer); - stream.* = .init(superblock.block_size, file_reader, blocks, buffer); + continue; + } + const blocks = try gpa.alloc(u32, size); + errdefer gpa.free(blocks); + for (blocks) |*block| { + const block_id = try directory.interface.takeInt(u32, .little); + // Index 0 is reserved for the superblock. + // In theory, every page which is `n * block_size + 1` or `n * block_size + 2` + // is also reserved, for one of the FPMs. However, LLVM has been observed to map + // these into actual streams, so allow it for compatibility. + if (block_id == 0 or block_id >= superblock.num_blocks) return error.InvalidBlockIndex; + block.* = block_id; } + const buffer = try gpa.alloc(u8, 64); + errdefer gpa.free(buffer); + stream.* = .init(superblock.block_size, file_reader, blocks, buffer); } const end = directory.logicalPos(); diff --git a/lib/std/debug/SelfInfo.zig b/lib/std/debug/SelfInfo.zig deleted file mode 100644 index f77d14b913..0000000000 --- a/lib/std/debug/SelfInfo.zig +++ /dev/null @@ -1,2238 +0,0 @@ -//! Cross-platform abstraction for this binary's own debug information, with a -//! goal of minimal code bloat and compilation speed penalty. - -const builtin = @import("builtin"); -const native_os = builtin.os.tag; -const native_endian = native_arch.endian(); -const native_arch = builtin.cpu.arch; - -const std = @import("../std.zig"); -const mem = std.mem; -const Allocator = std.mem.Allocator; -const windows = std.os.windows; -const macho = std.macho; -const fs = std.fs; -const coff = std.coff; -const pdb = std.pdb; -const assert = std.debug.assert; -const posix = std.posix; -const elf = std.elf; -const Dwarf = std.debug.Dwarf; -const Pdb = std.debug.Pdb; -const File = std.fs.File; -const math = std.math; -const testing = std.testing; -const StackIterator = std.debug.StackIterator; -const regBytes = Dwarf.abi.regBytes; -const regValueNative = Dwarf.abi.regValueNative; - -const SelfInfo = @This(); - -const root = @import("root"); - -allocator: Allocator, -address_map: std.AutoHashMap(usize, *Module), -modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModule) else void, - -pub const OpenError = error{ - MissingDebugInfo, - UnsupportedOperatingSystem, -} || @typeInfo(@typeInfo(@TypeOf(SelfInfo.init)).@"fn".return_type.?).error_union.error_set; - -pub fn open(allocator: Allocator) OpenError!SelfInfo { - nosuspend { - if (builtin.strip_debug_info) - return error.MissingDebugInfo; - switch (native_os) { - .linux, - .freebsd, - .netbsd, - .dragonfly, - .openbsd, - .macos, - .solaris, - .illumos, - .windows, - => return try SelfInfo.init(allocator), - else => return error.UnsupportedOperatingSystem, - } - } -} - -pub fn init(allocator: Allocator) !SelfInfo { - var debug_info: SelfInfo = .{ - .allocator = allocator, - .address_map = std.AutoHashMap(usize, *Module).init(allocator), - .modules = if (native_os == .windows) .{} else {}, - }; - - if (native_os == .windows) { - errdefer debug_info.modules.deinit(allocator); - - const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0); - if (handle == windows.INVALID_HANDLE_VALUE) { - switch (windows.GetLastError()) { - else => |err| return windows.unexpectedError(err), - } - } - defer windows.CloseHandle(handle); - - var module_entry: windows.MODULEENTRY32 = undefined; - module_entry.dwSize = @sizeOf(windows.MODULEENTRY32); - if (windows.kernel32.Module32First(handle, &module_entry) == 0) { - return error.MissingDebugInfo; - } - - var module_valid = true; - while (module_valid) { - const module_info = try debug_info.modules.addOne(allocator); - const name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{}; - errdefer allocator.free(name); - - module_info.* = .{ - .base_address = @intFromPtr(module_entry.modBaseAddr), - .size = module_entry.modBaseSize, - .name = name, - .handle = module_entry.hModule, - }; - - module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1; - } - } - - return debug_info; -} - -pub fn deinit(self: *SelfInfo) void { - var it = self.address_map.iterator(); - while (it.next()) |entry| { - const mdi = entry.value_ptr.*; - mdi.deinit(self.allocator); - self.allocator.destroy(mdi); - } - self.address_map.deinit(); - if (native_os == .windows) { - for (self.modules.items) |module| { - self.allocator.free(module.name); - if (module.mapped_file) |mapped_file| mapped_file.deinit(); - } - self.modules.deinit(self.allocator); - } -} - -pub fn getModuleForAddress(self: *SelfInfo, address: usize) !*Module { - if (builtin.target.os.tag.isDarwin()) { - return self.lookupModuleDyld(address); - } else if (native_os == .windows) { - return self.lookupModuleWin32(address); - } else if (native_os == .haiku) { - return self.lookupModuleHaiku(address); - } else if (builtin.target.cpu.arch.isWasm()) { - return self.lookupModuleWasm(address); - } else { - return self.lookupModuleDl(address); - } -} - -// Returns the module name for a given address. -// This can be called when getModuleForAddress fails, so implementations should provide -// a path that doesn't rely on any side-effects of a prior successful module lookup. -pub fn getModuleNameForAddress(self: *SelfInfo, address: usize) ?[]const u8 { - if (builtin.target.os.tag.isDarwin()) { - return self.lookupModuleNameDyld(address); - } else if (native_os == .windows) { - return self.lookupModuleNameWin32(address); - } else if (native_os == .haiku) { - return null; - } else if (builtin.target.cpu.arch.isWasm()) { - return null; - } else { - return self.lookupModuleNameDl(address); - } -} - -fn lookupModuleDyld(self: *SelfInfo, address: usize) !*Module { - const image_count = std.c._dyld_image_count(); - - var i: u32 = 0; - while (i < image_count) : (i += 1) { - const header = std.c._dyld_get_image_header(i) orelse continue; - const base_address = @intFromPtr(header); - if (address < base_address) continue; - const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i); - - var it = macho.LoadCommandIterator{ - .ncmds = header.ncmds, - .buffer = @alignCast(@as( - [*]u8, - @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)), - )[0..header.sizeofcmds]), - }; - - var unwind_info: ?[]const u8 = null; - var eh_frame: ?[]const u8 = null; - while (it.next()) |cmd| switch (cmd.cmd()) { - .SEGMENT_64 => { - const segment_cmd = cmd.cast(macho.segment_command_64).?; - if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue; - - const seg_start = segment_cmd.vmaddr + vmaddr_slide; - const seg_end = seg_start + segment_cmd.vmsize; - if (address >= seg_start and address < seg_end) { - if (self.address_map.get(base_address)) |obj_di| { - return obj_di; - } - - for (cmd.getSections()) |sect| { - const sect_addr: usize = @intCast(sect.addr); - const sect_size: usize = @intCast(sect.size); - if (mem.eql(u8, "__unwind_info", sect.sectName())) { - unwind_info = @as([*]const u8, @ptrFromInt(sect_addr + vmaddr_slide))[0..sect_size]; - } else if (mem.eql(u8, "__eh_frame", sect.sectName())) { - eh_frame = @as([*]const u8, @ptrFromInt(sect_addr + vmaddr_slide))[0..sect_size]; - } - } - - const obj_di = try self.allocator.create(Module); - errdefer self.allocator.destroy(obj_di); - - const macho_path = mem.sliceTo(std.c._dyld_get_image_name(i), 0); - const macho_file = fs.cwd().openFile(macho_path, .{}) catch |err| switch (err) { - error.FileNotFound => return error.MissingDebugInfo, - else => return err, - }; - obj_di.* = try readMachODebugInfo(self.allocator, macho_file); - obj_di.base_address = base_address; - obj_di.vmaddr_slide = vmaddr_slide; - obj_di.unwind_info = unwind_info; - obj_di.eh_frame = eh_frame; - - try self.address_map.putNoClobber(base_address, obj_di); - - return obj_di; - } - }, - else => {}, - }; - } - - return error.MissingDebugInfo; -} - -fn lookupModuleNameDyld(self: *SelfInfo, address: usize) ?[]const u8 { - _ = self; - const image_count = std.c._dyld_image_count(); - - var i: u32 = 0; - while (i < image_count) : (i += 1) { - const header = std.c._dyld_get_image_header(i) orelse continue; - const base_address = @intFromPtr(header); - if (address < base_address) continue; - const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i); - - var it = macho.LoadCommandIterator{ - .ncmds = header.ncmds, - .buffer = @alignCast(@as( - [*]u8, - @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)), - )[0..header.sizeofcmds]), - }; - - while (it.next()) |cmd| switch (cmd.cmd()) { - .SEGMENT_64 => { - const segment_cmd = cmd.cast(macho.segment_command_64).?; - if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue; - - const original_address = address - vmaddr_slide; - const seg_start = segment_cmd.vmaddr; - const seg_end = seg_start + segment_cmd.vmsize; - if (original_address >= seg_start and original_address < seg_end) { - return fs.path.basename(mem.sliceTo(std.c._dyld_get_image_name(i), 0)); - } - }, - else => {}, - }; - } - - return null; -} - -fn lookupModuleWin32(self: *SelfInfo, address: usize) !*Module { - for (self.modules.items) |*module| { - if (address >= module.base_address and address < module.base_address + module.size) { - if (self.address_map.get(module.base_address)) |obj_di| { - return obj_di; - } - - const obj_di = try self.allocator.create(Module); - errdefer self.allocator.destroy(obj_di); - - const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size]; - var coff_obj = try coff.Coff.init(mapped_module, true); - - // The string table is not mapped into memory by the loader, so if a section name is in the - // string table then we have to map the full image file from disk. This can happen when - // a binary is produced with -gdwarf, since the section names are longer than 8 bytes. - if (coff_obj.strtabRequired()) { - var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined; - // openFileAbsoluteW requires the prefix to be present - @memcpy(name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' }); - - const process_handle = windows.GetCurrentProcess(); - const len = windows.kernel32.GetModuleFileNameExW( - process_handle, - module.handle, - @ptrCast(&name_buffer[4]), - windows.PATH_MAX_WIDE, - ); - - if (len == 0) return error.MissingDebugInfo; - const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) { - error.FileNotFound => return error.MissingDebugInfo, - else => return err, - }; - errdefer coff_file.close(); - - var section_handle: windows.HANDLE = undefined; - const create_section_rc = windows.ntdll.NtCreateSection( - §ion_handle, - windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ, - null, - null, - windows.PAGE_READONLY, - // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default. - // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6. - windows.SEC_COMMIT, - coff_file.handle, - ); - if (create_section_rc != .SUCCESS) return error.MissingDebugInfo; - errdefer windows.CloseHandle(section_handle); - - var coff_len: usize = 0; - var base_ptr: usize = 0; - const map_section_rc = windows.ntdll.NtMapViewOfSection( - section_handle, - process_handle, - @ptrCast(&base_ptr), - null, - 0, - null, - &coff_len, - .ViewUnmap, - 0, - windows.PAGE_READONLY, - ); - if (map_section_rc != .SUCCESS) return error.MissingDebugInfo; - errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrFromInt(base_ptr)) == .SUCCESS); - - const section_view = @as([*]const u8, @ptrFromInt(base_ptr))[0..coff_len]; - coff_obj = try coff.Coff.init(section_view, false); - - module.mapped_file = .{ - .file = coff_file, - .section_handle = section_handle, - .section_view = section_view, - }; - } - errdefer if (module.mapped_file) |mapped_file| mapped_file.deinit(); - - obj_di.* = try readCoffDebugInfo(self.allocator, &coff_obj); - obj_di.base_address = module.base_address; - - try self.address_map.putNoClobber(module.base_address, obj_di); - return obj_di; - } - } - - return error.MissingDebugInfo; -} - -fn lookupModuleNameWin32(self: *SelfInfo, address: usize) ?[]const u8 { - for (self.modules.items) |module| { - if (address >= module.base_address and address < module.base_address + module.size) { - return module.name; - } - } - return null; -} - -fn lookupModuleNameDl(self: *SelfInfo, address: usize) ?[]const u8 { - _ = self; - - var ctx: struct { - // Input - address: usize, - // Output - name: []const u8 = "", - } = .{ .address = address }; - const CtxTy = @TypeOf(ctx); - - if (posix.dl_iterate_phdr(&ctx, error{Found}, struct { - fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void { - _ = size; - if (context.address < info.addr) return; - const phdrs = info.phdr[0..info.phnum]; - for (phdrs) |*phdr| { - if (phdr.p_type != elf.PT_LOAD) continue; - - const seg_start = info.addr +% phdr.p_vaddr; - const seg_end = seg_start + phdr.p_memsz; - if (context.address >= seg_start and context.address < seg_end) { - context.name = mem.sliceTo(info.name, 0) orelse ""; - break; - } - } else return; - - return error.Found; - } - }.callback)) { - return null; - } else |err| switch (err) { - error.Found => return fs.path.basename(ctx.name), - } - - return null; -} - -fn lookupModuleDl(self: *SelfInfo, address: usize) !*Module { - var ctx: struct { - // Input - address: usize, - // Output - base_address: usize = undefined, - name: []const u8 = undefined, - build_id: ?[]const u8 = null, - gnu_eh_frame: ?[]const u8 = null, - } = .{ .address = address }; - const CtxTy = @TypeOf(ctx); - - if (posix.dl_iterate_phdr(&ctx, error{Found}, struct { - fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void { - _ = size; - // The base address is too high - if (context.address < info.addr) - return; - - const phdrs = info.phdr[0..info.phnum]; - for (phdrs) |*phdr| { - if (phdr.p_type != elf.PT_LOAD) continue; - - // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000 - const seg_start = info.addr +% phdr.p_vaddr; - const seg_end = seg_start + phdr.p_memsz; - if (context.address >= seg_start and context.address < seg_end) { - // Android libc uses NULL instead of an empty string to mark the - // main program - context.name = mem.sliceTo(info.name, 0) orelse ""; - context.base_address = info.addr; - break; - } - } else return; - - for (info.phdr[0..info.phnum]) |phdr| { - switch (phdr.p_type) { - elf.PT_NOTE => { - // Look for .note.gnu.build-id - const note_bytes = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz]; - const name_size = mem.readInt(u32, note_bytes[0..4], native_endian); - if (name_size != 4) continue; - const desc_size = mem.readInt(u32, note_bytes[4..8], native_endian); - const note_type = mem.readInt(u32, note_bytes[8..12], native_endian); - if (note_type != elf.NT_GNU_BUILD_ID) continue; - if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue; - context.build_id = note_bytes[16..][0..desc_size]; - }, - elf.PT_GNU_EH_FRAME => { - context.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz]; - }, - else => {}, - } - } - - // Stop the iteration - return error.Found; - } - }.callback)) { - return error.MissingDebugInfo; - } else |err| switch (err) { - error.Found => {}, - } - - if (self.address_map.get(ctx.base_address)) |obj_di| { - return obj_di; - } - - const obj_di = try self.allocator.create(Module); - errdefer self.allocator.destroy(obj_di); - - var sections: Dwarf.SectionArray = Dwarf.null_section_array; - if (ctx.gnu_eh_frame) |eh_frame_hdr| { - // This is a special case - pointer offsets inside .eh_frame_hdr - // are encoded relative to its base address, so we must use the - // version that is already memory mapped, and not the one that - // will be mapped separately from the ELF file. - sections[@intFromEnum(Dwarf.Section.Id.eh_frame_hdr)] = .{ - .data = eh_frame_hdr, - .owned = false, - }; - } - - obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, §ions, null); - obj_di.base_address = ctx.base_address; - - // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding - obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.base_address) catch {}; - - try self.address_map.putNoClobber(ctx.base_address, obj_di); - - return obj_di; -} - -fn lookupModuleHaiku(self: *SelfInfo, address: usize) !*Module { - _ = self; - _ = address; - @panic("TODO implement lookup module for Haiku"); -} - -fn lookupModuleWasm(self: *SelfInfo, address: usize) !*Module { - _ = self; - _ = address; - @panic("TODO implement lookup module for Wasm"); -} - -pub const Module = switch (native_os) { - .macos, .ios, .watchos, .tvos, .visionos => struct { - base_address: usize, - vmaddr_slide: usize, - mapped_memory: []align(std.heap.page_size_min) const u8, - symbols: []const MachoSymbol, - strings: [:0]const u8, - ofiles: OFileTable, - - // Backed by the in-memory sections mapped by the loader - unwind_info: ?[]const u8 = null, - eh_frame: ?[]const u8 = null, - - const OFileTable = std.StringHashMap(OFileInfo); - const OFileInfo = struct { - di: Dwarf, - addr_table: std.StringHashMap(u64), - }; - - pub fn deinit(self: *@This(), allocator: Allocator) void { - var it = self.ofiles.iterator(); - while (it.next()) |entry| { - const ofile = entry.value_ptr; - ofile.di.deinit(allocator); - ofile.addr_table.deinit(); - } - self.ofiles.deinit(); - allocator.free(self.symbols); - posix.munmap(self.mapped_memory); - } - - fn loadOFile(self: *@This(), allocator: Allocator, o_file_path: []const u8) !*OFileInfo { - const o_file = try fs.cwd().openFile(o_file_path, .{}); - const mapped_mem = try mapWholeFile(o_file); - - const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr)); - if (hdr.magic != std.macho.MH_MAGIC_64) - return error.InvalidDebugInfo; - - var segcmd: ?macho.LoadCommandIterator.LoadCommand = null; - var symtabcmd: ?macho.symtab_command = null; - var it = macho.LoadCommandIterator{ - .ncmds = hdr.ncmds, - .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], - }; - while (it.next()) |cmd| switch (cmd.cmd()) { - .SEGMENT_64 => segcmd = cmd, - .SYMTAB => symtabcmd = cmd.cast(macho.symtab_command).?, - else => {}, - }; - - if (segcmd == null or symtabcmd == null) return error.MissingDebugInfo; - - // Parse symbols - const strtab = @as( - [*]const u8, - @ptrCast(&mapped_mem[symtabcmd.?.stroff]), - )[0 .. symtabcmd.?.strsize - 1 :0]; - const symtab = @as( - [*]const macho.nlist_64, - @ptrCast(@alignCast(&mapped_mem[symtabcmd.?.symoff])), - )[0..symtabcmd.?.nsyms]; - - // TODO handle tentative (common) symbols - var addr_table = std.StringHashMap(u64).init(allocator); - try addr_table.ensureTotalCapacity(@as(u32, @intCast(symtab.len))); - for (symtab) |sym| { - if (sym.n_strx == 0) continue; - if (sym.undf() or sym.tentative() or sym.abs()) continue; - const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0); - // TODO is it possible to have a symbol collision? - addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value); - } - - var sections: Dwarf.SectionArray = Dwarf.null_section_array; - if (self.eh_frame) |eh_frame| sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{ - .data = eh_frame, - .owned = false, - }; - - for (segcmd.?.getSections()) |sect| { - if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue; - - var section_index: ?usize = null; - inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| { - if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i; - } - if (section_index == null) continue; - - const section_bytes = try Dwarf.chopSlice(mapped_mem, sect.offset, sect.size); - sections[section_index.?] = .{ - .data = section_bytes, - .virtual_address = @intCast(sect.addr), - .owned = false, - }; - } - - const missing_debug_info = - sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or - sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or - sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or - sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; - if (missing_debug_info) return error.MissingDebugInfo; - - var di: Dwarf = .{ - .endian = .little, - .sections = sections, - .is_macho = true, - }; - - try Dwarf.open(&di, allocator); - const info = OFileInfo{ - .di = di, - .addr_table = addr_table, - }; - - // Add the debug info to the cache - const result = try self.ofiles.getOrPut(o_file_path); - assert(!result.found_existing); - result.value_ptr.* = info; - - return result.value_ptr; - } - - pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol { - nosuspend { - const result = try self.getOFileInfoForAddress(allocator, address); - if (result.symbol == null) return .{}; - - // Take the symbol name from the N_FUN STAB entry, we're going to - // use it if we fail to find the DWARF infos - const stab_symbol = mem.sliceTo(self.strings[result.symbol.?.strx..], 0); - if (result.o_file_info == null) return .{ .name = stab_symbol }; - - // Translate again the address, this time into an address inside the - // .o file - const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{ - .name = "???", - }; - - const addr_off = result.relocated_address - result.symbol.?.addr; - const o_file_di = &result.o_file_info.?.di; - if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| { - return .{ - .name = o_file_di.getSymbolName(relocated_address_o) orelse "???", - .compile_unit_name = compile_unit.die.getAttrString( - o_file_di, - std.dwarf.AT.name, - o_file_di.section(.debug_str), - compile_unit.*, - ) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => "???", - }, - .source_location = o_file_di.getLineNumberInfo( - allocator, - compile_unit, - relocated_address_o + addr_off, - ) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => null, - else => return err, - }, - }; - } else |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => { - return .{ .name = stab_symbol }; - }, - else => return err, - } - } - } - - pub fn getOFileInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !struct { - relocated_address: usize, - symbol: ?*const MachoSymbol = null, - o_file_info: ?*OFileInfo = null, - } { - nosuspend { - // Translate the VA into an address into this object - const relocated_address = address - self.vmaddr_slide; - - // Find the .o file where this symbol is defined - const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse return .{ - .relocated_address = relocated_address, - }; - - // Check if its debug infos are already in the cache - const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0); - const o_file_info = self.ofiles.getPtr(o_file_path) orelse - (self.loadOFile(allocator, o_file_path) catch |err| switch (err) { - error.FileNotFound, - error.MissingDebugInfo, - error.InvalidDebugInfo, - => return .{ - .relocated_address = relocated_address, - .symbol = symbol, - }, - else => return err, - }); - - return .{ - .relocated_address = relocated_address, - .symbol = symbol, - .o_file_info = o_file_info, - }; - } - } - - pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf { - return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null; - } - }, - .uefi, .windows => struct { - base_address: usize, - pdb: ?Pdb, - dwarf: ?Dwarf, - coff_image_base: u64, - - /// Only used if pdb is non-null - coff_section_headers: []coff.SectionHeader, - - pub fn deinit(self: *@This(), gpa: Allocator) void { - if (self.dwarf) |*dwarf| { - dwarf.deinit(gpa); - } - - if (self.pdb) |*p| { - gpa.free(p.file_reader.interface.buffer); - gpa.destroy(p.file_reader); - p.deinit(); - gpa.free(self.coff_section_headers); - } - - self.* = undefined; - } - - fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?std.debug.Symbol { - var coff_section: *align(1) const coff.SectionHeader = undefined; - const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| { - if (sect_contrib.section > self.coff_section_headers.len) continue; - // Remember that SectionContribEntry.Section is 1-based. - coff_section = &self.coff_section_headers[sect_contrib.section - 1]; - - const vaddr_start = coff_section.virtual_address + sect_contrib.offset; - const vaddr_end = vaddr_start + sect_contrib.size; - if (relocated_address >= vaddr_start and relocated_address < vaddr_end) { - break sect_contrib.module_index; - } - } else { - // we have no information to add to the address - return null; - }; - - const module = (try self.pdb.?.getModule(mod_index)) orelse - return error.InvalidDebugInfo; - const obj_basename = fs.path.basename(module.obj_file_name); - - const symbol_name = self.pdb.?.getSymbolName( - module, - relocated_address - coff_section.virtual_address, - ) orelse "???"; - const opt_line_info = try self.pdb.?.getLineNumberInfo( - module, - relocated_address - coff_section.virtual_address, - ); - - return .{ - .name = symbol_name, - .compile_unit_name = obj_basename, - .source_location = opt_line_info, - }; - } - - pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol { - // Translate the VA into an address into this object - const relocated_address = address - self.base_address; - - if (self.pdb != null) { - if (try self.getSymbolFromPdb(relocated_address)) |symbol| return symbol; - } - - if (self.dwarf) |*dwarf| { - const dwarf_address = relocated_address + self.coff_image_base; - return dwarf.getSymbol(allocator, dwarf_address); - } - - return .{}; - } - - pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf { - _ = allocator; - _ = address; - - return switch (self.debug_data) { - .dwarf => |*dwarf| dwarf, - else => null, - }; - } - }, - .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => Dwarf.ElfModule, - .wasi, .emscripten => struct { - pub fn deinit(self: *@This(), allocator: Allocator) void { - _ = self; - _ = allocator; - } - - pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol { - _ = self; - _ = allocator; - _ = address; - return .{}; - } - - pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf { - _ = self; - _ = allocator; - _ = address; - return null; - } - }, - else => Dwarf, -}; - -/// How is this different than `Module` when the host is Windows? -/// Why are both stored in the `SelfInfo` struct? -/// Boy, it sure would be nice if someone added documentation comments for this -/// struct explaining it. -pub const WindowsModule = struct { - base_address: usize, - size: u32, - name: []const u8, - handle: windows.HMODULE, - - // Set when the image file needed to be mapped from disk - mapped_file: ?struct { - file: File, - section_handle: windows.HANDLE, - section_view: []const u8, - - pub fn deinit(self: @This()) void { - const process_handle = windows.GetCurrentProcess(); - assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrCast(@constCast(self.section_view.ptr))) == .SUCCESS); - windows.CloseHandle(self.section_handle); - self.file.close(); - } - } = null, -}; - -/// This takes ownership of macho_file: users of this function should not close -/// it themselves, even on error. -/// TODO it's weird to take ownership even on error, rework this code. -fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module { - const mapped_mem = try mapWholeFile(macho_file); - - const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr)); - if (hdr.magic != macho.MH_MAGIC_64) - return error.InvalidDebugInfo; - - var it = macho.LoadCommandIterator{ - .ncmds = hdr.ncmds, - .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], - }; - const symtab = while (it.next()) |cmd| switch (cmd.cmd()) { - .SYMTAB => break cmd.cast(macho.symtab_command).?, - else => {}, - } else return error.MissingDebugInfo; - - const syms = @as( - [*]const macho.nlist_64, - @ptrCast(@alignCast(&mapped_mem[symtab.symoff])), - )[0..symtab.nsyms]; - const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0]; - - const symbols_buf = try allocator.alloc(MachoSymbol, syms.len); - - var ofile: u32 = undefined; - var last_sym: MachoSymbol = undefined; - var symbol_index: usize = 0; - var state: enum { - init, - oso_open, - oso_close, - bnsym, - fun_strx, - fun_size, - ensym, - } = .init; - - for (syms) |*sym| { - if (!sym.stab()) continue; - - // TODO handle globals N_GSYM, and statics N_STSYM - switch (sym.n_type) { - macho.N_OSO => { - switch (state) { - .init, .oso_close => { - state = .oso_open; - ofile = sym.n_strx; - }, - else => return error.InvalidDebugInfo, - } - }, - macho.N_BNSYM => { - switch (state) { - .oso_open, .ensym => { - state = .bnsym; - last_sym = .{ - .strx = 0, - .addr = sym.n_value, - .size = 0, - .ofile = ofile, - }; - }, - else => return error.InvalidDebugInfo, - } - }, - macho.N_FUN => { - switch (state) { - .bnsym => { - state = .fun_strx; - last_sym.strx = sym.n_strx; - }, - .fun_strx => { - state = .fun_size; - last_sym.size = @as(u32, @intCast(sym.n_value)); - }, - else => return error.InvalidDebugInfo, - } - }, - macho.N_ENSYM => { - switch (state) { - .fun_size => { - state = .ensym; - symbols_buf[symbol_index] = last_sym; - symbol_index += 1; - }, - else => return error.InvalidDebugInfo, - } - }, - macho.N_SO => { - switch (state) { - .init, .oso_close => {}, - .oso_open, .ensym => { - state = .oso_close; - }, - else => return error.InvalidDebugInfo, - } - }, - else => {}, - } - } - - switch (state) { - .init => return error.MissingDebugInfo, - .oso_close => {}, - else => return error.InvalidDebugInfo, - } - - const symbols = try allocator.realloc(symbols_buf, symbol_index); - - // Even though lld emits symbols in ascending order, this debug code - // should work for programs linked in any valid way. - // This sort is so that we can binary search later. - mem.sort(MachoSymbol, symbols, {}, MachoSymbol.addressLessThan); - - return .{ - .base_address = undefined, - .vmaddr_slide = undefined, - .mapped_memory = mapped_mem, - .ofiles = Module.OFileTable.init(allocator), - .symbols = symbols, - .strings = strings, - }; -} - -fn readCoffDebugInfo(gpa: Allocator, coff_obj: *coff.Coff) !Module { - nosuspend { - var di: Module = .{ - .base_address = undefined, - .coff_image_base = coff_obj.getImageBase(), - .coff_section_headers = undefined, - .pdb = null, - .dwarf = null, - }; - - if (coff_obj.getSectionByName(".debug_info")) |_| { - // This coff file has embedded DWARF debug info - var sections: Dwarf.SectionArray = Dwarf.null_section_array; - errdefer for (sections) |section| if (section) |s| if (s.owned) gpa.free(s.data); - - inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| { - sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: { - break :blk .{ - .data = try coff_obj.getSectionDataAlloc(section_header, gpa), - .virtual_address = section_header.virtual_address, - .owned = true, - }; - } else null; - } - - var dwarf: Dwarf = .{ - .endian = native_endian, - .sections = sections, - .is_macho = false, - }; - - try Dwarf.open(&dwarf, gpa); - di.dwarf = dwarf; - } - - const raw_path = try coff_obj.getPdbPath() orelse return di; - const path = blk: { - if (fs.path.isAbsolute(raw_path)) { - break :blk raw_path; - } else { - const self_dir = try fs.selfExeDirPathAlloc(gpa); - defer gpa.free(self_dir); - break :blk try fs.path.join(gpa, &.{ self_dir, raw_path }); - } - }; - defer if (path.ptr != raw_path.ptr) gpa.free(path); - - const pdb_file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) { - error.FileNotFound, error.IsDir => { - if (di.dwarf == null) return error.MissingDebugInfo; - return di; - }, - else => |e| return e, - }; - errdefer pdb_file.close(); - - const pdb_file_reader_buffer = try gpa.alloc(u8, 4096); - errdefer gpa.free(pdb_file_reader_buffer); - - const pdb_file_reader = try gpa.create(File.Reader); - errdefer gpa.destroy(pdb_file_reader); - - pdb_file_reader.* = pdb_file.reader(pdb_file_reader_buffer); - - di.pdb = try Pdb.init(gpa, pdb_file_reader); - try di.pdb.?.parseInfoStream(); - try di.pdb.?.parseDbiStream(); - - if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age) - return error.InvalidDebugInfo; - - // Only used by the pdb path - di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa); - errdefer gpa.free(di.coff_section_headers); - - return di; - } -} - -/// Reads debug info from an ELF file, or the current binary if none in specified. -/// If the required sections aren't present but a reference to external debug info is, -/// then this this function will recurse to attempt to load the debug sections from -/// an external file. -pub fn readElfDebugInfo( - allocator: Allocator, - elf_filename: ?[]const u8, - build_id: ?[]const u8, - expected_crc: ?u32, - parent_sections: *Dwarf.SectionArray, - parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8, -) !Dwarf.ElfModule { - nosuspend { - const elf_file = (if (elf_filename) |filename| blk: { - break :blk fs.cwd().openFile(filename, .{}); - } else fs.openSelfExe(.{})) catch |err| switch (err) { - error.FileNotFound => return error.MissingDebugInfo, - else => return err, - }; - - const mapped_mem = try mapWholeFile(elf_file); - return Dwarf.ElfModule.load( - allocator, - mapped_mem, - build_id, - expected_crc, - parent_sections, - parent_mapped_mem, - elf_filename, - ); - } -} - -const MachoSymbol = struct { - strx: u32, - addr: u64, - size: u32, - ofile: u32, - - /// Returns the address from the macho file - fn address(self: MachoSymbol) u64 { - return self.addr; - } - - fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool { - _ = context; - return lhs.addr < rhs.addr; - } -}; - -/// Takes ownership of file, even on error. -/// TODO it's weird to take ownership even on error, rework this code. -fn mapWholeFile(file: File) ![]align(std.heap.page_size_min) const u8 { - nosuspend { - defer file.close(); - - const file_len = math.cast(usize, try file.getEndPos()) orelse math.maxInt(usize); - const mapped_mem = try posix.mmap( - null, - file_len, - posix.PROT.READ, - .{ .TYPE = .SHARED }, - file.handle, - 0, - ); - errdefer posix.munmap(mapped_mem); - - return mapped_mem; - } -} - -fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol { - var min: usize = 0; - var max: usize = symbols.len - 1; - while (min < max) { - const mid = min + (max - min) / 2; - const curr = &symbols[mid]; - const next = &symbols[mid + 1]; - if (address >= next.address()) { - min = mid + 1; - } else if (address < curr.address()) { - max = mid; - } else { - return curr; - } - } - - const max_sym = &symbols[symbols.len - 1]; - if (address >= max_sym.address()) - return max_sym; - - return null; -} - -test machoSearchSymbols { - const symbols = [_]MachoSymbol{ - .{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined }, - .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined }, - .{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined }, - }; - - try testing.expectEqual(null, machoSearchSymbols(&symbols, 0)); - try testing.expectEqual(null, machoSearchSymbols(&symbols, 99)); - try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 100).?); - try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 150).?); - try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 199).?); - - try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 200).?); - try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 250).?); - try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 299).?); - - try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 300).?); - try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 301).?); - try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?); -} - -/// Unwind a frame using MachO compact unwind info (from __unwind_info). -/// If the compact encoding can't encode a way to unwind a frame, it will -/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available. -pub fn unwindFrameMachO( - allocator: Allocator, - base_address: usize, - context: *UnwindContext, - unwind_info: []const u8, - eh_frame: ?[]const u8, -) !usize { - const header = std.mem.bytesAsValue( - macho.unwind_info_section_header, - unwind_info[0..@sizeOf(macho.unwind_info_section_header)], - ); - const indices = std.mem.bytesAsSlice( - macho.unwind_info_section_header_index_entry, - unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry)], - ); - if (indices.len == 0) return error.MissingUnwindInfo; - - const mapped_pc = context.pc - base_address; - const second_level_index = blk: { - var left: usize = 0; - var len: usize = indices.len; - - while (len > 1) { - const mid = left + len / 2; - const offset = indices[mid].functionOffset; - if (mapped_pc < offset) { - len /= 2; - } else { - left = mid; - if (mapped_pc == offset) break; - len -= len / 2; - } - } - - // Last index is a sentinel containing the highest address as its functionOffset - if (indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo; - break :blk &indices[left]; - }; - - const common_encodings = std.mem.bytesAsSlice( - macho.compact_unwind_encoding_t, - unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t)], - ); - - const start_offset = second_level_index.secondLevelPagesSectionOffset; - const kind = std.mem.bytesAsValue( - macho.UNWIND_SECOND_LEVEL, - unwind_info[start_offset..][0..@sizeOf(macho.UNWIND_SECOND_LEVEL)], - ); - - const entry: struct { - function_offset: usize, - raw_encoding: u32, - } = switch (kind.*) { - .REGULAR => blk: { - const page_header = std.mem.bytesAsValue( - macho.unwind_info_regular_second_level_page_header, - unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_regular_second_level_page_header)], - ); - - const entries = std.mem.bytesAsSlice( - macho.unwind_info_regular_second_level_entry, - unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry)], - ); - if (entries.len == 0) return error.InvalidUnwindInfo; - - var left: usize = 0; - var len: usize = entries.len; - while (len > 1) { - const mid = left + len / 2; - const offset = entries[mid].functionOffset; - if (mapped_pc < offset) { - len /= 2; - } else { - left = mid; - if (mapped_pc == offset) break; - len -= len / 2; - } - } - - break :blk .{ - .function_offset = entries[left].functionOffset, - .raw_encoding = entries[left].encoding, - }; - }, - .COMPRESSED => blk: { - const page_header = std.mem.bytesAsValue( - macho.unwind_info_compressed_second_level_page_header, - unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_compressed_second_level_page_header)], - ); - - const entries = std.mem.bytesAsSlice( - macho.UnwindInfoCompressedEntry, - unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry)], - ); - if (entries.len == 0) return error.InvalidUnwindInfo; - - var left: usize = 0; - var len: usize = entries.len; - while (len > 1) { - const mid = left + len / 2; - const offset = second_level_index.functionOffset + entries[mid].funcOffset; - if (mapped_pc < offset) { - len /= 2; - } else { - left = mid; - if (mapped_pc == offset) break; - len -= len / 2; - } - } - - const entry = entries[left]; - const function_offset = second_level_index.functionOffset + entry.funcOffset; - if (entry.encodingIndex < header.commonEncodingsArrayCount) { - if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo; - break :blk .{ - .function_offset = function_offset, - .raw_encoding = common_encodings[entry.encodingIndex], - }; - } else { - const local_index = try math.sub( - u8, - entry.encodingIndex, - math.cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo, - ); - const local_encodings = std.mem.bytesAsSlice( - macho.compact_unwind_encoding_t, - unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t)], - ); - if (local_index >= local_encodings.len) return error.InvalidUnwindInfo; - break :blk .{ - .function_offset = function_offset, - .raw_encoding = local_encodings[local_index], - }; - } - }, - else => return error.InvalidUnwindInfo, - }; - - if (entry.raw_encoding == 0) return error.NoUnwindInfo; - const reg_context = Dwarf.abi.RegisterContext{ - .eh_frame = false, - .is_macho = true, - }; - - const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding); - const new_ip = switch (builtin.cpu.arch) { - .x86_64 => switch (encoding.mode.x86_64) { - .OLD => return error.UnimplementedUnwindEncoding, - .RBP_FRAME => blk: { - const regs: [5]u3 = .{ - encoding.value.x86_64.frame.reg0, - encoding.value.x86_64.frame.reg1, - encoding.value.x86_64.frame.reg2, - encoding.value.x86_64.frame.reg3, - encoding.value.x86_64.frame.reg4, - }; - - const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize); - var max_reg: usize = 0; - inline for (regs, 0..) |reg, i| { - if (reg > 0) max_reg = i; - } - - const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*; - const new_sp = fp + 2 * @sizeOf(usize); - - const ip_ptr = fp + @sizeOf(usize); - const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; - const new_fp = @as(*const usize, @ptrFromInt(fp)).*; - - (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp; - (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp; - (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip; - - for (regs, 0..) |reg, i| { - if (reg == 0) continue; - const addr = fp - frame_offset + i * @sizeOf(usize); - const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg); - (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*; - } - - break :blk new_ip; - }, - .STACK_IMMD, - .STACK_IND, - => blk: { - const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*; - const stack_size = if (encoding.mode.x86_64 == .STACK_IMMD) - @as(usize, encoding.value.x86_64.frameless.stack.direct.stack_size) * @sizeOf(usize) - else stack_size: { - // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function. - const sub_offset_addr = - base_address + - entry.function_offset + - encoding.value.x86_64.frameless.stack.indirect.sub_offset; - - // `sub_offset_addr` points to the offset of the literal within the instruction - const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*; - break :stack_size sub_operand + @sizeOf(usize) * @as(usize, encoding.value.x86_64.frameless.stack.indirect.stack_adjust); - }; - - // Decode the Lehmer-coded sequence of registers. - // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h - - // Decode the variable-based permutation number into its digits. Each digit represents - // an index into the list of register numbers that weren't yet used in the sequence at - // the time the digit was added. - const reg_count = encoding.value.x86_64.frameless.stack_reg_count; - const ip_ptr = if (reg_count > 0) reg_blk: { - var digits: [6]u3 = undefined; - var accumulator: usize = encoding.value.x86_64.frameless.stack_reg_permutation; - var base: usize = 2; - for (0..reg_count) |i| { - const div = accumulator / base; - digits[digits.len - 1 - i] = @intCast(accumulator - base * div); - accumulator = div; - base += 1; - } - - const reg_numbers = [_]u3{ 1, 2, 3, 4, 5, 6 }; - var registers: [reg_numbers.len]u3 = undefined; - var used_indices = [_]bool{false} ** reg_numbers.len; - for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| { - var unused_count: u8 = 0; - const unused_index = for (used_indices, 0..) |used, index| { - if (!used) { - if (target_unused_index == unused_count) break index; - unused_count += 1; - } - } else unreachable; - - registers[i] = reg_numbers[unused_index]; - used_indices[unused_index] = true; - } - - var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1); - for (0..reg_count) |i| { - const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]); - (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; - reg_addr += @sizeOf(usize); - } - - break :reg_blk reg_addr; - } else sp + stack_size - @sizeOf(usize); - - const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; - const new_sp = ip_ptr + @sizeOf(usize); - - (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp; - (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip; - - break :blk new_ip; - }, - .DWARF => { - return unwindFrameMachODwarf(allocator, base_address, context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf)); - }, - }, - .aarch64, .aarch64_be => switch (encoding.mode.arm64) { - .OLD => return error.UnimplementedUnwindEncoding, - .FRAMELESS => blk: { - const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*; - const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16; - const new_ip = (try regValueNative(context.thread_context, 30, reg_context)).*; - (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp; - break :blk new_ip; - }, - .DWARF => { - return unwindFrameMachODwarf(allocator, base_address, context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf)); - }, - .FRAME => blk: { - const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*; - const ip_ptr = fp + @sizeOf(usize); - - var reg_addr = fp - @sizeOf(usize); - inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.x_reg_pairs)).@"struct".fields, 0..) |field, i| { - if (@field(encoding.value.arm64.frame.x_reg_pairs, field.name) != 0) { - (try regValueNative(context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; - reg_addr += @sizeOf(usize); - (try regValueNative(context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; - reg_addr += @sizeOf(usize); - } - } - - inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.d_reg_pairs)).@"struct".fields, 0..) |field, i| { - if (@field(encoding.value.arm64.frame.d_reg_pairs, field.name) != 0) { - // Only the lower half of the 128-bit V registers are restored during unwinding - @memcpy( - try regBytes(context.thread_context, 64 + 8 + i, context.reg_context), - std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), - ); - reg_addr += @sizeOf(usize); - @memcpy( - try regBytes(context.thread_context, 64 + 9 + i, context.reg_context), - std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), - ); - reg_addr += @sizeOf(usize); - } - } - - const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; - const new_fp = @as(*const usize, @ptrFromInt(fp)).*; - - (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp; - (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip; - - break :blk new_ip; - }, - }, - else => return error.UnimplementedArch, - }; - - context.pc = stripInstructionPtrAuthCode(new_ip); - if (context.pc > 0) context.pc -= 1; - return new_ip; -} - -pub const UnwindContext = struct { - allocator: Allocator, - cfa: ?usize, - pc: usize, - thread_context: *std.debug.ThreadContext, - reg_context: Dwarf.abi.RegisterContext, - vm: VirtualMachine, - stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }), - - pub fn init( - allocator: Allocator, - thread_context: *std.debug.ThreadContext, - ) !UnwindContext { - comptime assert(supports_unwinding); - - const pc = stripInstructionPtrAuthCode( - (try regValueNative(thread_context, ip_reg_num, null)).*, - ); - - const context_copy = try allocator.create(std.debug.ThreadContext); - std.debug.copyContext(thread_context, context_copy); - - return .{ - .allocator = allocator, - .cfa = null, - .pc = pc, - .thread_context = context_copy, - .reg_context = undefined, - .vm = .{}, - .stack_machine = .{}, - }; - } - - pub fn deinit(self: *UnwindContext) void { - self.vm.deinit(self.allocator); - self.stack_machine.deinit(self.allocator); - self.allocator.destroy(self.thread_context); - self.* = undefined; - } - - pub fn getFp(self: *const UnwindContext) !usize { - return (try regValueNative(self.thread_context, fpRegNum(self.reg_context), self.reg_context)).*; - } -}; - -/// Some platforms use pointer authentication - the upper bits of instruction pointers contain a signature. -/// This function clears these signature bits to make the pointer usable. -pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize { - if (native_arch.isAARCH64()) { - // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it) - // The save / restore is because `xpaclri` operates on x30 (LR) - return asm ( - \\mov x16, x30 - \\mov x30, x15 - \\hint 0x07 - \\mov x15, x30 - \\mov x30, x16 - : [ret] "={x15}" (-> usize), - : [ptr] "{x15}" (ptr), - : .{ .x16 = true }); - } - - return ptr; -} - -/// Unwind a stack frame using DWARF unwinding info, updating the register context. -/// -/// If `.eh_frame_hdr` is available and complete, it will be used to binary search for the FDE. -/// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. The latter -/// may require lazily loading the data in those sections. -/// -/// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info -/// defers unwinding to DWARF. This is an offset into the `.eh_frame` section. -pub fn unwindFrameDwarf( - allocator: Allocator, - di: *Dwarf, - base_address: usize, - context: *UnwindContext, - explicit_fde_offset: ?usize, -) !usize { - if (!supports_unwinding) return error.UnsupportedCpuArchitecture; - if (context.pc == 0) return 0; - - const endian = di.endian; - - // Find the FDE and CIE - const cie, const fde = if (explicit_fde_offset) |fde_offset| blk: { - const dwarf_section: Dwarf.Section.Id = .eh_frame; - const frame_section = di.section(dwarf_section) orelse return error.MissingFDE; - if (fde_offset >= frame_section.len) return error.MissingFDE; - - var fbr: std.Io.Reader = .fixed(frame_section); - fbr.seek = fde_offset; - - const fde_entry_header = try Dwarf.EntryHeader.read(&fbr, dwarf_section, endian); - if (fde_entry_header.type != .fde) return error.MissingFDE; - - const cie_offset = fde_entry_header.type.fde; - fbr.seek = @intCast(cie_offset); - - const cie_entry_header = try Dwarf.EntryHeader.read(&fbr, dwarf_section, endian); - if (cie_entry_header.type != .cie) return Dwarf.bad(); - - const cie = try Dwarf.CommonInformationEntry.parse( - cie_entry_header.entry_bytes, - 0, - true, - cie_entry_header.format, - dwarf_section, - cie_entry_header.length_offset, - @sizeOf(usize), - native_endian, - ); - const fde = try Dwarf.FrameDescriptionEntry.parse( - fde_entry_header.entry_bytes, - 0, - true, - cie, - @sizeOf(usize), - native_endian, - ); - - break :blk .{ cie, fde }; - } else blk: { - // `.eh_frame_hdr` may be incomplete. We'll try it first, but if the lookup fails, we fall - // back to loading `.eh_frame`/`.debug_frame` and using those from that point on. - - if (di.eh_frame_hdr) |header| hdr: { - const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else { - try di.scanCieFdeInfo(allocator, base_address); - di.eh_frame_hdr = null; - break :hdr; - }; - - var cie: Dwarf.CommonInformationEntry = undefined; - var fde: Dwarf.FrameDescriptionEntry = undefined; - - header.findEntry( - eh_frame_len, - @intFromPtr(di.section(.eh_frame_hdr).?.ptr), - context.pc, - &cie, - &fde, - endian, - ) catch |err| switch (err) { - error.MissingDebugInfo => { - // `.eh_frame_hdr` appears to be incomplete, so go ahead and populate `cie_map` - // and `fde_list`, and fall back to the binary search logic below. - try di.scanCieFdeInfo(allocator, base_address); - - // Since `.eh_frame_hdr` is incomplete, we're very likely to get more lookup - // failures using it, and we've just built a complete, sorted list of FDEs - // anyway, so just stop using `.eh_frame_hdr` altogether. - di.eh_frame_hdr = null; - - break :hdr; - }, - else => return err, - }; - - break :blk .{ cie, fde }; - } - - const index = std.sort.binarySearch(Dwarf.FrameDescriptionEntry, di.fde_list.items, context.pc, struct { - pub fn compareFn(pc: usize, item: Dwarf.FrameDescriptionEntry) std.math.Order { - if (pc < item.pc_begin) return .lt; - - const range_end = item.pc_begin + item.pc_range; - if (pc < range_end) return .eq; - - return .gt; - } - }.compareFn); - - const fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE; - const cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE; - - break :blk .{ cie, fde }; - }; - - var expression_context: Dwarf.expression.Context = .{ - .format = cie.format, - .compile_unit = di.findCompileUnit(fde.pc_begin) catch null, - .thread_context = context.thread_context, - .reg_context = context.reg_context, - .cfa = context.cfa, - }; - - context.vm.reset(); - context.reg_context.eh_frame = cie.version != 4; - context.reg_context.is_macho = di.is_macho; - - const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde); - context.cfa = switch (row.cfa.rule) { - .val_offset => |offset| blk: { - const register = row.cfa.register orelse return error.InvalidCFARule; - const value = mem.readInt(usize, (try regBytes(context.thread_context, register, context.reg_context))[0..@sizeOf(usize)], native_endian); - break :blk try applyOffset(value, offset); - }, - .expression => |expr| blk: { - context.stack_machine.reset(); - const value = try context.stack_machine.run( - expr, - context.allocator, - expression_context, - context.cfa, - ); - - if (value) |v| { - if (v != .generic) return error.InvalidExpressionValue; - break :blk v.generic; - } else return error.NoExpressionValue; - }, - else => return error.InvalidCFARule, - }; - - expression_context.cfa = context.cfa; - - // Buffering the modifications is done because copying the thread context is not portable, - // some implementations (ie. darwin) use internal pointers to the mcontext. - var arena = std.heap.ArenaAllocator.init(context.allocator); - defer arena.deinit(); - const update_allocator = arena.allocator(); - - const RegisterUpdate = struct { - // Backed by thread_context - dest: []u8, - // Backed by arena - src: []const u8, - prev: ?*@This(), - }; - - var update_tail: ?*RegisterUpdate = null; - var has_return_address = true; - for (context.vm.rowColumns(row)) |column| { - if (column.register) |register| { - if (register == cie.return_address_register) { - has_return_address = column.rule != .undefined; - } - - const dest = try regBytes(context.thread_context, register, context.reg_context); - const src = try update_allocator.alloc(u8, dest.len); - - const prev = update_tail; - update_tail = try update_allocator.create(RegisterUpdate); - update_tail.?.* = .{ - .dest = dest, - .src = src, - .prev = prev, - }; - - try column.resolveValue(context, expression_context, src); - } - } - - // On all implemented architectures, the CFA is defined as being the previous frame's SP - (try regValueNative(context.thread_context, spRegNum(context.reg_context), context.reg_context)).* = context.cfa.?; - - while (update_tail) |tail| { - @memcpy(tail.dest, tail.src); - update_tail = tail.prev; - } - - if (has_return_address) { - context.pc = stripInstructionPtrAuthCode(mem.readInt(usize, (try regBytes( - context.thread_context, - cie.return_address_register, - context.reg_context, - ))[0..@sizeOf(usize)], native_endian)); - } else { - context.pc = 0; - } - - (try regValueNative(context.thread_context, ip_reg_num, context.reg_context)).* = context.pc; - - // The call instruction will have pushed the address of the instruction that follows the call as the return address. - // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in - // the function was the call). If we were to look up an FDE entry using the return address directly, it could end up - // either not finding an FDE at all, or using the next FDE in the program, producing incorrect results. To prevent this, - // we subtract one so that the next lookup is guaranteed to land inside the - // - // The exception to this rule is signal frames, where we return execution would be returned to the instruction - // that triggered the handler. - const return_address = context.pc; - if (context.pc > 0 and !cie.isSignalFrame()) context.pc -= 1; - - return return_address; -} - -fn fpRegNum(reg_context: Dwarf.abi.RegisterContext) u8 { - return Dwarf.abi.fpRegNum(native_arch, reg_context); -} - -fn spRegNum(reg_context: Dwarf.abi.RegisterContext) u8 { - return Dwarf.abi.spRegNum(native_arch, reg_context); -} - -const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?; - -/// Tells whether unwinding for the host is implemented. -pub const supports_unwinding = supportsUnwinding(&builtin.target); - -comptime { - if (supports_unwinding) assert(Dwarf.abi.supportsUnwinding(&builtin.target)); -} - -/// Tells whether unwinding for this target is *implemented* here in the Zig -/// standard library. -/// -/// See also `Dwarf.abi.supportsUnwinding` which tells whether Dwarf supports -/// unwinding on that target *in theory*. -pub fn supportsUnwinding(target: *const std.Target) bool { - return switch (target.cpu.arch) { - .x86 => switch (target.os.tag) { - .linux, .netbsd, .solaris, .illumos => true, - else => false, - }, - .x86_64 => switch (target.os.tag) { - .linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true, - else => false, - }, - .arm, .armeb, .thumb, .thumbeb => switch (target.os.tag) { - .linux => true, - else => false, - }, - .aarch64, .aarch64_be => switch (target.os.tag) { - .linux, .netbsd, .freebsd, .macos, .ios => true, - else => false, - }, - // Unwinding is possible on other targets but this implementation does - // not support them...yet! - else => false, - }; -} - -fn unwindFrameMachODwarf( - allocator: Allocator, - base_address: usize, - context: *UnwindContext, - eh_frame: []const u8, - fde_offset: usize, -) !usize { - var di: Dwarf = .{ - .endian = native_endian, - .is_macho = true, - }; - defer di.deinit(context.allocator); - - di.sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{ - .data = eh_frame, - .owned = false, - }; - - return unwindFrameDwarf(allocator, &di, base_address, context, fde_offset); -} - -/// This is a virtual machine that runs DWARF call frame instructions. -pub const VirtualMachine = struct { - /// See section 6.4.1 of the DWARF5 specification for details on each - const RegisterRule = union(enum) { - // The spec says that the default rule for each column is the undefined rule. - // However, it also allows ABI / compiler authors to specify alternate defaults, so - // there is a distinction made here. - default: void, - undefined: void, - same_value: void, - // offset(N) - offset: i64, - // val_offset(N) - val_offset: i64, - // register(R) - register: u8, - // expression(E) - expression: []const u8, - // val_expression(E) - val_expression: []const u8, - // Augmenter-defined rule - architectural: void, - }; - - /// Each row contains unwinding rules for a set of registers. - pub const Row = struct { - /// Offset from `FrameDescriptionEntry.pc_begin` - offset: u64 = 0, - /// Special-case column that defines the CFA (Canonical Frame Address) rule. - /// The register field of this column defines the register that CFA is derived from. - cfa: Column = .{}, - /// The register fields in these columns define the register the rule applies to. - columns: ColumnRange = .{}, - /// Indicates that the next write to any column in this row needs to copy - /// the backing column storage first, as it may be referenced by previous rows. - copy_on_write: bool = false, - }; - - pub const Column = struct { - register: ?u8 = null, - rule: RegisterRule = .{ .default = {} }, - - /// Resolves the register rule and places the result into `out` (see regBytes) - pub fn resolveValue( - self: Column, - context: *SelfInfo.UnwindContext, - expression_context: std.debug.Dwarf.expression.Context, - out: []u8, - ) !void { - switch (self.rule) { - .default => { - const register = self.register orelse return error.InvalidRegister; - try getRegDefaultValue(register, context, out); - }, - .undefined => { - @memset(out, undefined); - }, - .same_value => { - // TODO: This copy could be eliminated if callers always copy the state then call this function to update it - const register = self.register orelse return error.InvalidRegister; - const src = try regBytes(context.thread_context, register, context.reg_context); - if (src.len != out.len) return error.RegisterSizeMismatch; - @memcpy(out, src); - }, - .offset => |offset| { - if (context.cfa) |cfa| { - const addr = try applyOffset(cfa, offset); - const ptr: *const usize = @ptrFromInt(addr); - mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian); - } else return error.InvalidCFA; - }, - .val_offset => |offset| { - if (context.cfa) |cfa| { - mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian); - } else return error.InvalidCFA; - }, - .register => |register| { - const src = try regBytes(context.thread_context, register, context.reg_context); - if (src.len != out.len) return error.RegisterSizeMismatch; - @memcpy(out, try regBytes(context.thread_context, register, context.reg_context)); - }, - .expression => |expression| { - context.stack_machine.reset(); - const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?); - const addr = if (value) |v| blk: { - if (v != .generic) return error.InvalidExpressionValue; - break :blk v.generic; - } else return error.NoExpressionValue; - - const ptr: *usize = @ptrFromInt(addr); - mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian); - }, - .val_expression => |expression| { - context.stack_machine.reset(); - const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?); - if (value) |v| { - if (v != .generic) return error.InvalidExpressionValue; - mem.writeInt(usize, out[0..@sizeOf(usize)], v.generic, native_endian); - } else return error.NoExpressionValue; - }, - .architectural => return error.UnimplementedRegisterRule, - } - } - }; - - const ColumnRange = struct { - /// Index into `columns` of the first column in this row. - start: usize = undefined, - len: u8 = 0, - }; - - columns: std.ArrayListUnmanaged(Column) = .empty, - stack: std.ArrayListUnmanaged(ColumnRange) = .empty, - current_row: Row = .{}, - - /// The result of executing the CIE's initial_instructions - cie_row: ?Row = null, - - pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void { - self.stack.deinit(allocator); - self.columns.deinit(allocator); - self.* = undefined; - } - - pub fn reset(self: *VirtualMachine) void { - self.stack.clearRetainingCapacity(); - self.columns.clearRetainingCapacity(); - self.current_row = .{}; - self.cie_row = null; - } - - /// Return a slice backed by the row's non-CFA columns - pub fn rowColumns(self: VirtualMachine, row: Row) []Column { - if (row.columns.len == 0) return &.{}; - return self.columns.items[row.columns.start..][0..row.columns.len]; - } - - /// Either retrieves or adds a column for `register` (non-CFA) in the current row. - fn getOrAddColumn(self: *VirtualMachine, allocator: std.mem.Allocator, register: u8) !*Column { - for (self.rowColumns(self.current_row)) |*c| { - if (c.register == register) return c; - } - - if (self.current_row.columns.len == 0) { - self.current_row.columns.start = self.columns.items.len; - } - self.current_row.columns.len += 1; - - const column = try self.columns.addOne(allocator); - column.* = .{ - .register = register, - }; - - return column; - } - - /// Runs the CIE instructions, then the FDE instructions. Execution halts - /// once the row that corresponds to `pc` is known, and the row is returned. - pub fn runTo( - self: *VirtualMachine, - allocator: std.mem.Allocator, - pc: u64, - cie: std.debug.Dwarf.CommonInformationEntry, - fde: std.debug.Dwarf.FrameDescriptionEntry, - addr_size_bytes: u8, - endian: std.builtin.Endian, - ) !Row { - assert(self.cie_row == null); - if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return error.AddressOutOfRange; - - var prev_row: Row = self.current_row; - - var cie_stream: std.Io.Reader = .fixed(cie.initial_instructions); - var fde_stream: std.Io.Reader = .fixed(fde.instructions); - const streams = [_]*std.Io.Reader{ &cie_stream, &fde_stream }; - - for (&streams, 0..) |stream, i| { - while (stream.seek < stream.buffer.len) { - const instruction = try std.debug.Dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian); - prev_row = try self.step(allocator, cie, i == 0, instruction); - if (pc < fde.pc_begin + self.current_row.offset) return prev_row; - } - } - - return self.current_row; - } - - pub fn runToNative( - self: *VirtualMachine, - allocator: std.mem.Allocator, - pc: u64, - cie: std.debug.Dwarf.CommonInformationEntry, - fde: std.debug.Dwarf.FrameDescriptionEntry, - ) !Row { - return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), native_endian); - } - - fn resolveCopyOnWrite(self: *VirtualMachine, allocator: std.mem.Allocator) !void { - if (!self.current_row.copy_on_write) return; - - const new_start = self.columns.items.len; - if (self.current_row.columns.len > 0) { - try self.columns.ensureUnusedCapacity(allocator, self.current_row.columns.len); - self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row)); - self.current_row.columns.start = new_start; - } - } - - /// Executes a single instruction. - /// If this instruction is from the CIE, `is_initial` should be set. - /// Returns the value of `current_row` before executing this instruction. - pub fn step( - self: *VirtualMachine, - allocator: std.mem.Allocator, - cie: std.debug.Dwarf.CommonInformationEntry, - is_initial: bool, - instruction: Dwarf.call_frame.Instruction, - ) !Row { - // CIE instructions must be run before FDE instructions - assert(!is_initial or self.cie_row == null); - if (!is_initial and self.cie_row == null) { - self.cie_row = self.current_row; - self.current_row.copy_on_write = true; - } - - const prev_row = self.current_row; - switch (instruction) { - .set_loc => |i| { - if (i.address <= self.current_row.offset) return error.InvalidOperation; - // TODO: Check cie.segment_selector_size != 0 for DWARFV4 - self.current_row.offset = i.address; - }, - inline .advance_loc, - .advance_loc1, - .advance_loc2, - .advance_loc4, - => |i| { - self.current_row.offset += i.delta * cie.code_alignment_factor; - self.current_row.copy_on_write = true; - }, - inline .offset, - .offset_extended, - .offset_extended_sf, - => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ .offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor }; - }, - inline .restore, - .restore_extended, - => |i| { - try self.resolveCopyOnWrite(allocator); - if (self.cie_row) |cie_row| { - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = for (self.rowColumns(cie_row)) |cie_column| { - if (cie_column.register == i.register) break cie_column.rule; - } else .{ .default = {} }; - } else return error.InvalidOperation; - }, - .nop => {}, - .undefined => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ .undefined = {} }; - }, - .same_value => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ .same_value = {} }; - }, - .register => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ .register = i.target_register }; - }, - .remember_state => { - try self.stack.append(allocator, self.current_row.columns); - self.current_row.copy_on_write = true; - }, - .restore_state => { - const restored_columns = self.stack.pop() orelse return error.InvalidOperation; - self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len); - try self.columns.ensureUnusedCapacity(allocator, restored_columns.len); - - self.current_row.columns.start = self.columns.items.len; - self.current_row.columns.len = restored_columns.len; - self.columns.appendSliceAssumeCapacity(self.columns.items[restored_columns.start..][0..restored_columns.len]); - }, - .def_cfa => |i| { - try self.resolveCopyOnWrite(allocator); - self.current_row.cfa = .{ - .register = i.register, - .rule = .{ .val_offset = @intCast(i.offset) }, - }; - }, - .def_cfa_sf => |i| { - try self.resolveCopyOnWrite(allocator); - self.current_row.cfa = .{ - .register = i.register, - .rule = .{ .val_offset = i.offset * cie.data_alignment_factor }, - }; - }, - .def_cfa_register => |i| { - try self.resolveCopyOnWrite(allocator); - if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; - self.current_row.cfa.register = i.register; - }, - .def_cfa_offset => |i| { - try self.resolveCopyOnWrite(allocator); - if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; - self.current_row.cfa.rule = .{ - .val_offset = @intCast(i.offset), - }; - }, - .def_cfa_offset_sf => |i| { - try self.resolveCopyOnWrite(allocator); - if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; - self.current_row.cfa.rule = .{ - .val_offset = i.offset * cie.data_alignment_factor, - }; - }, - .def_cfa_expression => |i| { - try self.resolveCopyOnWrite(allocator); - self.current_row.cfa.register = undefined; - self.current_row.cfa.rule = .{ - .expression = i.block, - }; - }, - .expression => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ - .expression = i.block, - }; - }, - .val_offset => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ - .val_offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor, - }; - }, - .val_offset_sf => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ - .val_offset = i.offset * cie.data_alignment_factor, - }; - }, - .val_expression => |i| { - try self.resolveCopyOnWrite(allocator); - const column = try self.getOrAddColumn(allocator, i.register); - column.rule = .{ - .val_expression = i.block, - }; - }, - } - - return prev_row; - } -}; - -/// Returns the ABI-defined default value this register has in the unwinding table -/// before running any of the CIE instructions. The DWARF spec defines these as having -/// the .undefined rule by default, but allows ABI authors to override that. -fn getRegDefaultValue(reg_number: u8, context: *UnwindContext, out: []u8) !void { - switch (builtin.cpu.arch) { - .aarch64, .aarch64_be => { - // Callee-saved registers are initialized as if they had the .same_value rule - if (reg_number >= 19 and reg_number <= 28) { - const src = try regBytes(context.thread_context, reg_number, context.reg_context); - if (src.len != out.len) return error.RegisterSizeMismatch; - @memcpy(out, src); - return; - } - }, - else => {}, - } - - @memset(out, undefined); -} - -/// Since register rules are applied (usually) during a panic, -/// checked addition / subtraction is used so that we can return -/// an error and fall back to FP-based unwinding. -fn applyOffset(base: usize, offset: i64) !usize { - return if (offset >= 0) - try std.math.add(usize, base, @as(usize, @intCast(offset))) - else - try std.math.sub(usize, base, @as(usize, @intCast(-offset))); -} diff --git a/lib/std/debug/SelfInfo/Elf.zig b/lib/std/debug/SelfInfo/Elf.zig new file mode 100644 index 0000000000..f10dc3cd63 --- /dev/null +++ b/lib/std/debug/SelfInfo/Elf.zig @@ -0,0 +1,507 @@ +rwlock: std.Thread.RwLock, + +modules: std.ArrayList(Module), +ranges: std.ArrayList(Module.Range), + +unwind_cache: if (can_unwind) ?[]Dwarf.SelfUnwinder.CacheEntry else ?noreturn, + +pub const init: SelfInfo = .{ + .rwlock = .{}, + .modules = .empty, + .ranges = .empty, + .unwind_cache = null, +}; +pub fn deinit(si: *SelfInfo, gpa: Allocator) void { + for (si.modules.items) |*mod| { + unwind: { + const u = &(mod.unwind orelse break :unwind catch break :unwind); + for (u.buf[0..u.len]) |*unwind| unwind.deinit(gpa); + } + loaded: { + const l = &(mod.loaded_elf orelse break :loaded catch break :loaded); + l.file.deinit(gpa); + } + } + + si.modules.deinit(gpa); + si.ranges.deinit(gpa); + if (si.unwind_cache) |cache| gpa.free(cache); +} + +pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol { + const module = try si.findModule(gpa, address, .exclusive); + defer si.rwlock.unlock(); + + const vaddr = address - module.load_offset; + + const loaded_elf = try module.getLoadedElf(gpa); + if (loaded_elf.file.dwarf) |*dwarf| { + if (!loaded_elf.scanned_dwarf) { + dwarf.open(gpa, native_endian) catch |err| switch (err) { + error.InvalidDebugInfo, + error.MissingDebugInfo, + error.OutOfMemory, + => |e| return e, + error.EndOfStream, + error.Overflow, + error.ReadFailed, + error.StreamTooLong, + => return error.InvalidDebugInfo, + }; + loaded_elf.scanned_dwarf = true; + } + if (dwarf.getSymbol(gpa, native_endian, vaddr)) |sym| { + return sym; + } else |err| switch (err) { + error.MissingDebugInfo => {}, + + error.InvalidDebugInfo, + error.OutOfMemory, + => |e| return e, + + error.ReadFailed, + error.EndOfStream, + error.Overflow, + error.StreamTooLong, + => return error.InvalidDebugInfo, + } + } + // When DWARF is unavailable, fall back to searching the symtab. + return loaded_elf.file.searchSymtab(gpa, vaddr) catch |err| switch (err) { + error.NoSymtab, error.NoStrtab => return error.MissingDebugInfo, + error.BadSymtab => return error.InvalidDebugInfo, + error.OutOfMemory => |e| return e, + }; +} +pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 { + const module = try si.findModule(gpa, address, .shared); + defer si.rwlock.unlockShared(); + if (module.name.len == 0) return error.MissingDebugInfo; + return module.name; +} + +pub const can_unwind: bool = s: { + // The DWARF code can't deal with ILP32 ABIs yet: https://github.com/ziglang/zig/issues/25447 + switch (builtin.target.abi) { + .gnuabin32, + .muslabin32, + .gnux32, + .muslx32, + => break :s false, + else => {}, + } + + // Notably, we are yet to support unwinding on ARM. There, unwinding is not done through + // `.eh_frame`, but instead with the `.ARM.exidx` section, which has a different format. + const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) { + // Not supported yet: arm, m68k, sparc64 + .haiku => &.{ + .aarch64, + .powerpc, + .riscv64, + .x86, + .x86_64, + }, + // Not supported yet: arc, arm/armeb/thumb/thumbeb, csky, m68k, or1k, sparc/sparc64, xtensa + .linux => &.{ + .aarch64, + .aarch64_be, + .hexagon, + .loongarch64, + .mips, + .mipsel, + .mips64, + .mips64el, + .powerpc, + .powerpcle, + .powerpc64, + .powerpc64le, + .riscv32, + .riscv64, + .s390x, + .x86, + .x86_64, + }, + .serenity => &.{ + .aarch64, + .x86_64, + .riscv64, + }, + + .dragonfly => &.{ + .x86_64, + }, + // Not supported yet: arm + .freebsd => &.{ + .aarch64, + .powerpc64, + .powerpc64le, + .riscv64, + .x86_64, + }, + // Not supported yet: arm/armeb, m68k, mips64/mips64el, sparc/sparc64 + .netbsd => &.{ + .aarch64, + .aarch64_be, + .mips, + .mipsel, + .powerpc, + .x86, + .x86_64, + }, + // Not supported yet: arm, sparc64 + .openbsd => &.{ + .aarch64, + .mips64, + .mips64el, + .powerpc, + .powerpc64, + .riscv64, + .x86, + .x86_64, + }, + + .illumos => &.{ + .x86, + .x86_64, + }, + // Not supported yet: sparc64 + .solaris => &.{ + .x86_64, + }, + + else => unreachable, + }; + for (archs) |a| { + if (builtin.target.cpu.arch == a) break :s true; + } + break :s false; +}; +comptime { + if (can_unwind) { + std.debug.assert(Dwarf.supportsUnwinding(&builtin.target)); + } +} +pub const UnwindContext = Dwarf.SelfUnwinder; +pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize { + comptime assert(can_unwind); + + { + si.rwlock.lockShared(); + defer si.rwlock.unlockShared(); + if (si.unwind_cache) |cache| { + if (Dwarf.SelfUnwinder.CacheEntry.find(cache, context.pc)) |entry| { + return context.next(gpa, entry); + } + } + } + + const module = try si.findModule(gpa, context.pc, .exclusive); + defer si.rwlock.unlock(); + + if (si.unwind_cache == null) { + si.unwind_cache = try gpa.alloc(Dwarf.SelfUnwinder.CacheEntry, 2048); + @memset(si.unwind_cache.?, .empty); + } + + const unwind_sections = try module.getUnwindSections(gpa); + for (unwind_sections) |*unwind| { + if (context.computeRules(gpa, unwind, module.load_offset, null)) |entry| { + entry.populate(si.unwind_cache.?); + return context.next(gpa, &entry); + } else |err| switch (err) { + error.MissingDebugInfo => continue, + + error.InvalidDebugInfo, + error.UnsupportedDebugInfo, + error.OutOfMemory, + => |e| return e, + + error.EndOfStream, + error.StreamTooLong, + error.ReadFailed, + error.Overflow, + error.InvalidOpcode, + error.InvalidOperation, + error.InvalidOperand, + => return error.InvalidDebugInfo, + + error.UnimplementedUserOpcode, + error.UnsupportedAddrSize, + => return error.UnsupportedDebugInfo, + } + } + return error.MissingDebugInfo; +} + +const Module = struct { + load_offset: usize, + name: []const u8, + build_id: ?[]const u8, + gnu_eh_frame: ?[]const u8, + + /// `null` means unwind information has not yet been loaded. + unwind: ?(Error!UnwindSections), + + /// `null` means the ELF file has not yet been loaded. + loaded_elf: ?(Error!LoadedElf), + + const LoadedElf = struct { + file: std.debug.ElfFile, + scanned_dwarf: bool, + }; + + const UnwindSections = struct { + buf: [2]Dwarf.Unwind, + len: usize, + }; + + const Range = struct { + start: usize, + len: usize, + /// Index into `modules` + module_index: usize, + }; + + /// Assumes we already hold an exclusive lock. + fn getUnwindSections(mod: *Module, gpa: Allocator) Error![]Dwarf.Unwind { + if (mod.unwind == null) mod.unwind = loadUnwindSections(mod, gpa); + const us = &(mod.unwind.? catch |err| return err); + return us.buf[0..us.len]; + } + fn loadUnwindSections(mod: *Module, gpa: Allocator) Error!UnwindSections { + var us: UnwindSections = .{ + .buf = undefined, + .len = 0, + }; + if (mod.gnu_eh_frame) |section_bytes| { + const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - mod.load_offset; + const header = Dwarf.Unwind.EhFrameHeader.parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian) catch |err| switch (err) { + error.ReadFailed => unreachable, // it's all fixed buffers + error.InvalidDebugInfo => |e| return e, + error.EndOfStream, error.Overflow => return error.InvalidDebugInfo, + error.UnsupportedAddrSize => return error.UnsupportedDebugInfo, + }; + us.buf[us.len] = .initEhFrameHdr(header, section_vaddr, @ptrFromInt(@as(usize, @intCast(mod.load_offset + header.eh_frame_vaddr)))); + us.len += 1; + } else { + // There is no `.eh_frame_hdr` section. There may still be an `.eh_frame` or `.debug_frame` + // section, but we'll have to load the binary to get at it. + const loaded = try mod.getLoadedElf(gpa); + // If both are present, we can't just pick one -- the info could be split between them. + // `.debug_frame` is likely to be the more complete section, so we'll prioritize that one. + if (loaded.file.debug_frame) |*debug_frame| { + us.buf[us.len] = .initSection(.debug_frame, debug_frame.vaddr, debug_frame.bytes); + us.len += 1; + } + if (loaded.file.eh_frame) |*eh_frame| { + us.buf[us.len] = .initSection(.eh_frame, eh_frame.vaddr, eh_frame.bytes); + us.len += 1; + } + } + errdefer for (us.buf[0..us.len]) |*u| u.deinit(gpa); + for (us.buf[0..us.len]) |*u| u.prepare(gpa, @sizeOf(usize), native_endian, true, false) catch |err| switch (err) { + error.ReadFailed => unreachable, // it's all fixed buffers + error.InvalidDebugInfo, + error.MissingDebugInfo, + error.OutOfMemory, + => |e| return e, + error.EndOfStream, + error.Overflow, + error.StreamTooLong, + error.InvalidOperand, + error.InvalidOpcode, + error.InvalidOperation, + => return error.InvalidDebugInfo, + error.UnsupportedAddrSize, + error.UnsupportedDwarfVersion, + error.UnimplementedUserOpcode, + => return error.UnsupportedDebugInfo, + }; + return us; + } + + /// Assumes we already hold an exclusive lock. + fn getLoadedElf(mod: *Module, gpa: Allocator) Error!*LoadedElf { + if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa); + return if (mod.loaded_elf.?) |*elf| elf else |err| err; + } + fn loadElf(mod: *Module, gpa: Allocator) Error!LoadedElf { + const load_result = if (mod.name.len > 0) res: { + var file = std.fs.cwd().openFile(mod.name, .{}) catch return error.MissingDebugInfo; + defer file.close(); + break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(mod.name)); + } else res: { + const path = std.fs.selfExePathAlloc(gpa) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + else => return error.ReadFailed, + }; + defer gpa.free(path); + var file = std.fs.cwd().openFile(path, .{}) catch return error.MissingDebugInfo; + defer file.close(); + break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(path)); + }; + + var elf_file = load_result catch |err| switch (err) { + error.OutOfMemory, + error.Unexpected, + => |e| return e, + + error.Overflow, + error.TruncatedElfFile, + error.InvalidCompressedSection, + error.InvalidElfMagic, + error.InvalidElfVersion, + error.InvalidElfClass, + error.InvalidElfEndian, + => return error.InvalidDebugInfo, + + error.SystemResources, + error.MemoryMappingNotSupported, + error.AccessDenied, + error.LockedMemoryLimitExceeded, + error.ProcessFdQuotaExceeded, + error.SystemFdQuotaExceeded, + => return error.ReadFailed, + }; + errdefer elf_file.deinit(gpa); + + if (elf_file.endian != native_endian) return error.InvalidDebugInfo; + if (elf_file.is_64 != (@sizeOf(usize) == 8)) return error.InvalidDebugInfo; + + return .{ + .file = elf_file, + .scanned_dwarf = false, + }; + } +}; + +fn findModule(si: *SelfInfo, gpa: Allocator, address: usize, lock: enum { shared, exclusive }) Error!*Module { + // With the requested lock, scan the module ranges looking for `address`. + switch (lock) { + .shared => si.rwlock.lockShared(), + .exclusive => si.rwlock.lock(), + } + for (si.ranges.items) |*range| { + if (address >= range.start and address < range.start + range.len) { + return &si.modules.items[range.module_index]; + } + } + // The address wasn't in a known range. We will rebuild the module/range lists, since it's possible + // a new module was loaded. Upgrade to an exclusive lock if necessary. + switch (lock) { + .shared => { + si.rwlock.unlockShared(); + si.rwlock.lock(); + }, + .exclusive => {}, + } + // Rebuild module list with the exclusive lock. + { + errdefer si.rwlock.unlock(); + for (si.modules.items) |*mod| { + unwind: { + const u = &(mod.unwind orelse break :unwind catch break :unwind); + for (u.buf[0..u.len]) |*unwind| unwind.deinit(gpa); + } + loaded: { + const l = &(mod.loaded_elf orelse break :loaded catch break :loaded); + l.file.deinit(gpa); + } + } + si.modules.clearRetainingCapacity(); + si.ranges.clearRetainingCapacity(); + var ctx: DlIterContext = .{ .si = si, .gpa = gpa }; + try std.posix.dl_iterate_phdr(&ctx, error{OutOfMemory}, DlIterContext.callback); + } + // Downgrade the lock back to shared if necessary. + switch (lock) { + .shared => { + si.rwlock.unlock(); + si.rwlock.lockShared(); + }, + .exclusive => {}, + } + // Scan the newly rebuilt module ranges. + for (si.ranges.items) |*range| { + if (address >= range.start and address < range.start + range.len) { + return &si.modules.items[range.module_index]; + } + } + // Still nothing; unlock and error. + switch (lock) { + .shared => si.rwlock.unlockShared(), + .exclusive => si.rwlock.unlock(), + } + return error.MissingDebugInfo; +} +const DlIterContext = struct { + si: *SelfInfo, + gpa: Allocator, + + fn callback(info: *std.posix.dl_phdr_info, size: usize, context: *@This()) !void { + _ = size; + + var build_id: ?[]const u8 = null; + var gnu_eh_frame: ?[]const u8 = null; + + // Populate `build_id` and `gnu_eh_frame` + for (info.phdr[0..info.phnum]) |phdr| { + switch (phdr.p_type) { + std.elf.PT_NOTE => { + // Look for .note.gnu.build-id + const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr); + var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]); + const name_size = r.takeInt(u32, native_endian) catch continue; + const desc_size = r.takeInt(u32, native_endian) catch continue; + const note_type = r.takeInt(u32, native_endian) catch continue; + const name = r.take(name_size) catch continue; + if (note_type != std.elf.NT_GNU_BUILD_ID) continue; + if (!std.mem.eql(u8, name, "GNU\x00")) continue; + const desc = r.take(desc_size) catch continue; + build_id = desc; + }, + std.elf.PT_GNU_EH_FRAME => { + const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr); + gnu_eh_frame = segment_ptr[0..phdr.p_memsz]; + }, + else => {}, + } + } + + const gpa = context.gpa; + const si = context.si; + + const module_index = si.modules.items.len; + try si.modules.append(gpa, .{ + .load_offset = info.addr, + // Android libc uses NULL instead of "" to mark the main program + .name = std.mem.sliceTo(info.name, 0) orelse "", + .build_id = build_id, + .gnu_eh_frame = gnu_eh_frame, + .unwind = null, + .loaded_elf = null, + }); + + for (info.phdr[0..info.phnum]) |phdr| { + if (phdr.p_type != std.elf.PT_LOAD) continue; + try context.si.ranges.append(gpa, .{ + // Overflowing addition handles VSDOs having p_vaddr = 0xffffffffff700000 + .start = info.addr +% phdr.p_vaddr, + .len = phdr.p_memsz, + .module_index = module_index, + }); + } + } +}; + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const Dwarf = std.debug.Dwarf; +const Error = std.debug.SelfInfoError; +const assert = std.debug.assert; + +const builtin = @import("builtin"); +const native_endian = builtin.target.cpu.arch.endian(); + +const SelfInfo = @This(); diff --git a/lib/std/debug/SelfInfo/MachO.zig b/lib/std/debug/SelfInfo/MachO.zig new file mode 100644 index 0000000000..a89a2f0fb5 --- /dev/null +++ b/lib/std/debug/SelfInfo/MachO.zig @@ -0,0 +1,983 @@ +mutex: std.Thread.Mutex, +/// Accessed through `Module.Adapter`. +modules: std.ArrayHashMapUnmanaged(Module, void, Module.Context, false), +ofiles: std.StringArrayHashMapUnmanaged(?OFile), + +pub const init: SelfInfo = .{ + .mutex = .{}, + .modules = .empty, + .ofiles = .empty, +}; +pub fn deinit(si: *SelfInfo, gpa: Allocator) void { + for (si.modules.keys()) |*module| { + unwind: { + const u = &(module.unwind orelse break :unwind catch break :unwind); + if (u.dwarf) |*dwarf| dwarf.deinit(gpa); + } + loaded: { + const l = &(module.loaded_macho orelse break :loaded catch break :loaded); + gpa.free(l.symbols); + posix.munmap(l.mapped_memory); + } + } + for (si.ofiles.values()) |*opt_ofile| { + const ofile = &(opt_ofile.* orelse continue); + ofile.dwarf.deinit(gpa); + ofile.symbols_by_name.deinit(gpa); + posix.munmap(ofile.mapped_memory); + } + si.modules.deinit(gpa); + si.ofiles.deinit(gpa); +} + +pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol { + const module = try si.findModule(gpa, address); + defer si.mutex.unlock(); + + const loaded_macho = try module.getLoadedMachO(gpa); + + const vaddr = address - loaded_macho.vaddr_offset; + const symbol = MachoSymbol.find(loaded_macho.symbols, vaddr) orelse return .unknown; + + // offset of `address` from start of `symbol` + const address_symbol_offset = vaddr - symbol.addr; + + // Take the symbol name from the N_FUN STAB entry, we're going to + // use it if we fail to find the DWARF infos + const stab_symbol = mem.sliceTo(loaded_macho.strings[symbol.strx..], 0); + + // If any information is missing, we can at least return this from now on. + const sym_only_result: std.debug.Symbol = .{ + .name = stab_symbol, + .compile_unit_name = null, + .source_location = null, + }; + + if (symbol.ofile == MachoSymbol.unknown_ofile) { + // We don't have STAB info, so can't track down the object file; all we can do is the symbol name. + return sym_only_result; + } + + const o_file: *OFile = of: { + const path = mem.sliceTo(loaded_macho.strings[symbol.ofile..], 0); + const gop = try si.ofiles.getOrPut(gpa, path); + if (!gop.found_existing) { + gop.value_ptr.* = loadOFile(gpa, path) catch null; + } + if (gop.value_ptr.*) |*o_file| { + break :of o_file; + } else { + return sym_only_result; + } + }; + + const symbol_index = o_file.symbols_by_name.getKeyAdapted( + @as([]const u8, stab_symbol), + @as(OFile.SymbolAdapter, .{ .strtab = o_file.strtab, .symtab = o_file.symtab }), + ) orelse return sym_only_result; + const symbol_ofile_vaddr = o_file.symtab[symbol_index].n_value; + + const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch return sym_only_result; + + return .{ + .name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr + address_symbol_offset) orelse stab_symbol, + .compile_unit_name = compile_unit.die.getAttrString( + &o_file.dwarf, + native_endian, + std.dwarf.AT.name, + o_file.dwarf.section(.debug_str), + compile_unit, + ) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => null, + }, + .source_location = o_file.dwarf.getLineNumberInfo( + gpa, + native_endian, + compile_unit, + symbol_ofile_vaddr + address_symbol_offset, + ) catch null, + }; +} +pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 { + const module = try si.findModule(gpa, address); + defer si.mutex.unlock(); + return module.name; +} + +pub const can_unwind: bool = true; +pub const UnwindContext = std.debug.Dwarf.SelfUnwinder; +/// Unwind a frame using MachO compact unwind info (from `__unwind_info`). +/// If the compact encoding can't encode a way to unwind a frame, it will +/// defer unwinding to DWARF, in which case `__eh_frame` will be used if available. +pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize { + return unwindFrameInner(si, gpa, context) catch |err| switch (err) { + error.InvalidDebugInfo, + error.MissingDebugInfo, + error.UnsupportedDebugInfo, + error.ReadFailed, + error.OutOfMemory, + error.Unexpected, + => |e| return e, + error.UnsupportedRegister, + error.UnsupportedAddrSize, + error.UnimplementedUserOpcode, + => return error.UnsupportedDebugInfo, + error.Overflow, + error.EndOfStream, + error.StreamTooLong, + error.InvalidOpcode, + error.InvalidOperation, + error.InvalidOperand, + error.InvalidRegister, + error.IncompatibleRegisterSize, + => return error.InvalidDebugInfo, + }; +} +fn unwindFrameInner(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize { + const module = try si.findModule(gpa, context.pc); + defer si.mutex.unlock(); + + const unwind: *Module.Unwind = try module.getUnwindInfo(gpa); + + const ip_reg_num = comptime Dwarf.ipRegNum(builtin.target.cpu.arch).?; + const fp_reg_num = comptime Dwarf.fpRegNum(builtin.target.cpu.arch); + const sp_reg_num = comptime Dwarf.spRegNum(builtin.target.cpu.arch); + + const unwind_info = unwind.unwind_info orelse return error.MissingDebugInfo; + if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidDebugInfo; + const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info); + + const index_byte_count = header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry); + if (unwind_info.len < header.indexSectionOffset + index_byte_count) return error.InvalidDebugInfo; + const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]); + if (indices.len == 0) return error.MissingDebugInfo; + + // offset of the PC into the `__TEXT` segment + const pc_text_offset = context.pc - module.text_base; + + const start_offset: u32, const first_level_offset: u32 = index: { + var left: usize = 0; + var len: usize = indices.len; + while (len > 1) { + const mid = left + len / 2; + if (pc_text_offset < indices[mid].functionOffset) { + len /= 2; + } else { + left = mid; + len -= len / 2; + } + } + break :index .{ indices[left].secondLevelPagesSectionOffset, indices[left].functionOffset }; + }; + // An offset of 0 is a sentinel indicating a range does not have unwind info. + if (start_offset == 0) return error.MissingDebugInfo; + + const common_encodings_byte_count = header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t); + if (unwind_info.len < header.commonEncodingsArraySectionOffset + common_encodings_byte_count) return error.InvalidDebugInfo; + const common_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast( + unwind_info[header.commonEncodingsArraySectionOffset..][0..common_encodings_byte_count], + ); + + if (unwind_info.len < start_offset + @sizeOf(macho.UNWIND_SECOND_LEVEL)) return error.InvalidDebugInfo; + const kind: *align(1) const macho.UNWIND_SECOND_LEVEL = @ptrCast(unwind_info[start_offset..]); + + const entry: struct { + function_offset: usize, + raw_encoding: u32, + } = switch (kind.*) { + .REGULAR => entry: { + if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_regular_second_level_page_header)) return error.InvalidDebugInfo; + const page_header: *align(1) const macho.unwind_info_regular_second_level_page_header = @ptrCast(unwind_info[start_offset..]); + + const entries_byte_count = page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry); + if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo; + const entries: []align(1) const macho.unwind_info_regular_second_level_entry = @ptrCast( + unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count], + ); + if (entries.len == 0) return error.InvalidDebugInfo; + + var left: usize = 0; + var len: usize = entries.len; + while (len > 1) { + const mid = left + len / 2; + if (pc_text_offset < entries[mid].functionOffset) { + len /= 2; + } else { + left = mid; + len -= len / 2; + } + } + break :entry .{ + .function_offset = entries[left].functionOffset, + .raw_encoding = entries[left].encoding, + }; + }, + .COMPRESSED => entry: { + if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_compressed_second_level_page_header)) return error.InvalidDebugInfo; + const page_header: *align(1) const macho.unwind_info_compressed_second_level_page_header = @ptrCast(unwind_info[start_offset..]); + + const entries_byte_count = page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry); + if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo; + const entries: []align(1) const macho.UnwindInfoCompressedEntry = @ptrCast( + unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count], + ); + if (entries.len == 0) return error.InvalidDebugInfo; + + var left: usize = 0; + var len: usize = entries.len; + while (len > 1) { + const mid = left + len / 2; + if (pc_text_offset < first_level_offset + entries[mid].funcOffset) { + len /= 2; + } else { + left = mid; + len -= len / 2; + } + } + const entry = entries[left]; + + const function_offset = first_level_offset + entry.funcOffset; + if (entry.encodingIndex < common_encodings.len) { + break :entry .{ + .function_offset = function_offset, + .raw_encoding = common_encodings[entry.encodingIndex], + }; + } + + const local_index = entry.encodingIndex - common_encodings.len; + const local_encodings_byte_count = page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t); + if (unwind_info.len < start_offset + page_header.encodingsPageOffset + local_encodings_byte_count) return error.InvalidDebugInfo; + const local_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast( + unwind_info[start_offset + page_header.encodingsPageOffset ..][0..local_encodings_byte_count], + ); + if (local_index >= local_encodings.len) return error.InvalidDebugInfo; + break :entry .{ + .function_offset = function_offset, + .raw_encoding = local_encodings[local_index], + }; + }, + else => return error.InvalidDebugInfo, + }; + + if (entry.raw_encoding == 0) return error.MissingDebugInfo; + + const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding); + const new_ip = switch (builtin.cpu.arch) { + .x86_64 => switch (encoding.mode.x86_64) { + .OLD => return error.UnsupportedDebugInfo, + .RBP_FRAME => ip: { + const frame = encoding.value.x86_64.frame; + + const fp = (try dwarfRegNative(&context.cpu_state, fp_reg_num)).*; + const new_sp = fp + 2 * @sizeOf(usize); + + const ip_ptr = fp + @sizeOf(usize); + const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; + const new_fp = @as(*const usize, @ptrFromInt(fp)).*; + + (try dwarfRegNative(&context.cpu_state, fp_reg_num)).* = new_fp; + (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp; + (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip; + + const regs: [5]u3 = .{ + frame.reg0, + frame.reg1, + frame.reg2, + frame.reg3, + frame.reg4, + }; + for (regs, 0..) |reg, i| { + if (reg == 0) continue; + const addr = fp - frame.frame_offset * @sizeOf(usize) + i * @sizeOf(usize); + const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg); + (try dwarfRegNative(&context.cpu_state, reg_number)).* = @as(*const usize, @ptrFromInt(addr)).*; + } + + break :ip new_ip; + }, + .STACK_IMMD, + .STACK_IND, + => ip: { + const frameless = encoding.value.x86_64.frameless; + + const sp = (try dwarfRegNative(&context.cpu_state, sp_reg_num)).*; + const stack_size: usize = stack_size: { + if (encoding.mode.x86_64 == .STACK_IMMD) { + break :stack_size @as(usize, frameless.stack.direct.stack_size) * @sizeOf(usize); + } + // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function. + const sub_offset_addr = + module.text_base + + entry.function_offset + + frameless.stack.indirect.sub_offset; + // `sub_offset_addr` points to the offset of the literal within the instruction + const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*; + break :stack_size sub_operand + @sizeOf(usize) * @as(usize, frameless.stack.indirect.stack_adjust); + }; + + // Decode the Lehmer-coded sequence of registers. + // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h + + // Decode the variable-based permutation number into its digits. Each digit represents + // an index into the list of register numbers that weren't yet used in the sequence at + // the time the digit was added. + const reg_count = frameless.stack_reg_count; + const ip_ptr = ip_ptr: { + var digits: [6]u3 = undefined; + var accumulator: usize = frameless.stack_reg_permutation; + var base: usize = 2; + for (0..reg_count) |i| { + const div = accumulator / base; + digits[digits.len - 1 - i] = @intCast(accumulator - base * div); + accumulator = div; + base += 1; + } + + var registers: [6]u3 = undefined; + var used_indices: [6]bool = @splat(false); + for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| { + var unused_count: u8 = 0; + const unused_index = for (used_indices, 0..) |used, index| { + if (!used) { + if (target_unused_index == unused_count) break index; + unused_count += 1; + } + } else unreachable; + registers[i] = @intCast(unused_index + 1); + used_indices[unused_index] = true; + } + + var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1); + for (0..reg_count) |i| { + const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]); + (try dwarfRegNative(&context.cpu_state, reg_number)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; + reg_addr += @sizeOf(usize); + } + + break :ip_ptr reg_addr; + }; + + const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; + const new_sp = ip_ptr + @sizeOf(usize); + + (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp; + (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip; + + break :ip new_ip; + }, + .DWARF => { + const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo); + const rules = try context.computeRules(gpa, dwarf, unwind.vmaddr_slide, encoding.value.x86_64.dwarf); + return context.next(gpa, &rules); + }, + }, + .aarch64 => switch (encoding.mode.arm64) { + .OLD => return error.UnsupportedDebugInfo, + .FRAMELESS => ip: { + const sp = (try dwarfRegNative(&context.cpu_state, sp_reg_num)).*; + const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16; + const new_ip = (try dwarfRegNative(&context.cpu_state, 30)).*; + (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp; + break :ip new_ip; + }, + .DWARF => { + const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo); + const rules = try context.computeRules(gpa, dwarf, unwind.vmaddr_slide, encoding.value.arm64.dwarf); + return context.next(gpa, &rules); + }, + .FRAME => ip: { + const frame = encoding.value.arm64.frame; + + const fp = (try dwarfRegNative(&context.cpu_state, fp_reg_num)).*; + const ip_ptr = fp + @sizeOf(usize); + + var reg_addr = fp - @sizeOf(usize); + inline for (@typeInfo(@TypeOf(frame.x_reg_pairs)).@"struct".fields, 0..) |field, i| { + if (@field(frame.x_reg_pairs, field.name) != 0) { + (try dwarfRegNative(&context.cpu_state, 19 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; + reg_addr += @sizeOf(usize); + (try dwarfRegNative(&context.cpu_state, 20 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; + reg_addr += @sizeOf(usize); + } + } + + // We intentionally skip restoring `frame.d_reg_pairs`; we know we don't support + // vector registers in the AArch64 `cpu_context` anyway, so there's no reason to + // fail a legitimate unwind just because we're asked to restore the registers here. + // If some weird/broken unwind info tells us to read them later, we will fail then. + reg_addr += 16 * @as(usize, @popCount(@as(u4, @bitCast(frame.d_reg_pairs)))); + + const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; + const new_fp = @as(*const usize, @ptrFromInt(fp)).*; + + (try dwarfRegNative(&context.cpu_state, fp_reg_num)).* = new_fp; + (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip; + + break :ip new_ip; + }, + }, + else => comptime unreachable, // unimplemented + }; + + const ret_addr = std.debug.stripInstructionPtrAuthCode(new_ip); + + // Like `Dwarf.SelfUnwinder.next`, adjust our next lookup pc in case the `call` was this + // function's last instruction making `ret_addr` one byte past its end. + context.pc = ret_addr -| 1; + + return ret_addr; +} + +/// Acquires the mutex on success. +fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) Error!*Module { + var info: std.c.dl_info = undefined; + if (std.c.dladdr(@ptrFromInt(address), &info) == 0) { + return error.MissingDebugInfo; + } + si.mutex.lock(); + errdefer si.mutex.unlock(); + const gop = try si.modules.getOrPutAdapted(gpa, @intFromPtr(info.fbase), Module.Adapter{}); + errdefer comptime unreachable; + if (!gop.found_existing) { + gop.key_ptr.* = .{ + .text_base = @intFromPtr(info.fbase), + .name = std.mem.span(info.fname), + .unwind = null, + .loaded_macho = null, + }; + } + return gop.key_ptr; +} + +const Module = struct { + text_base: usize, + name: []const u8, + unwind: ?(Error!Unwind), + loaded_macho: ?(Error!LoadedMachO), + + const Adapter = struct { + pub fn hash(_: Adapter, text_base: usize) u32 { + return @truncate(std.hash.int(text_base)); + } + pub fn eql(_: Adapter, a_text_base: usize, b_module: Module, b_index: usize) bool { + _ = b_index; + return a_text_base == b_module.text_base; + } + }; + const Context = struct { + pub fn hash(_: Context, module: Module) u32 { + return @truncate(std.hash.int(module.text_base)); + } + pub fn eql(_: Context, a_module: Module, b_module: Module, b_index: usize) bool { + _ = b_index; + return a_module.text_base == b_module.text_base; + } + }; + + const Unwind = struct { + /// The slide applied to the `__unwind_info` and `__eh_frame` sections. + /// So, `unwind_info.ptr` is this many bytes higher than the section's vmaddr. + vmaddr_slide: u64, + /// Backed by the in-memory section mapped by the loader. + unwind_info: ?[]const u8, + /// Backed by the in-memory `__eh_frame` section mapped by the loader. + dwarf: ?Dwarf.Unwind, + }; + + const LoadedMachO = struct { + mapped_memory: []align(std.heap.page_size_min) const u8, + symbols: []const MachoSymbol, + strings: []const u8, + /// This is not necessarily the same as the vmaddr_slide that dyld would report. This is + /// because the segments in the file on disk might differ from the ones in memory. Normally + /// we wouldn't necessarily expect that to work, but /usr/lib/dyld is incredibly annoying: + /// it exists on disk (necessarily, because the kernel needs to load it!), but is also in + /// the dyld cache (dyld actually restart itself from cache after loading it), and the two + /// versions have (very) different segment base addresses. It's sort of like a large slide + /// has been applied to all addresses in memory. For an optimal experience, we consider the + /// on-disk vmaddr instead of the in-memory one. + vaddr_offset: usize, + }; + + fn getUnwindInfo(module: *Module, gpa: Allocator) Error!*Unwind { + if (module.unwind == null) module.unwind = loadUnwindInfo(module, gpa); + return if (module.unwind.?) |*unwind| unwind else |err| err; + } + fn loadUnwindInfo(module: *const Module, gpa: Allocator) Error!Unwind { + const header: *std.macho.mach_header = @ptrFromInt(module.text_base); + + var it: macho.LoadCommandIterator = .{ + .ncmds = header.ncmds, + .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds], + }; + const sections, const text_vmaddr = while (it.next()) |load_cmd| { + if (load_cmd.cmd() != .SEGMENT_64) continue; + const segment_cmd = load_cmd.cast(macho.segment_command_64).?; + if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue; + break .{ load_cmd.getSections(), segment_cmd.vmaddr }; + } else unreachable; + + const vmaddr_slide = module.text_base - text_vmaddr; + + var opt_unwind_info: ?[]const u8 = null; + var opt_eh_frame: ?[]const u8 = null; + for (sections) |sect| { + if (mem.eql(u8, sect.sectName(), "__unwind_info")) { + const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr))); + opt_unwind_info = sect_ptr[0..@intCast(sect.size)]; + } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) { + const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr))); + opt_eh_frame = sect_ptr[0..@intCast(sect.size)]; + } + } + const eh_frame = opt_eh_frame orelse return .{ + .vmaddr_slide = vmaddr_slide, + .unwind_info = opt_unwind_info, + .dwarf = null, + }; + var dwarf: Dwarf.Unwind = .initSection(.eh_frame, @intFromPtr(eh_frame.ptr) - vmaddr_slide, eh_frame); + errdefer dwarf.deinit(gpa); + // We don't need lookups, so this call is just for scanning CIEs. + dwarf.prepare(gpa, @sizeOf(usize), native_endian, false, true) catch |err| switch (err) { + error.ReadFailed => unreachable, // it's all fixed buffers + error.InvalidDebugInfo, + error.MissingDebugInfo, + error.OutOfMemory, + => |e| return e, + error.EndOfStream, + error.Overflow, + error.StreamTooLong, + error.InvalidOperand, + error.InvalidOpcode, + error.InvalidOperation, + => return error.InvalidDebugInfo, + error.UnsupportedAddrSize, + error.UnsupportedDwarfVersion, + error.UnimplementedUserOpcode, + => return error.UnsupportedDebugInfo, + }; + + return .{ + .vmaddr_slide = vmaddr_slide, + .unwind_info = opt_unwind_info, + .dwarf = dwarf, + }; + } + + fn getLoadedMachO(module: *Module, gpa: Allocator) Error!*LoadedMachO { + if (module.loaded_macho == null) module.loaded_macho = loadMachO(module, gpa) catch |err| switch (err) { + error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory, error.Unexpected => |e| e, + else => error.ReadFailed, + }; + return if (module.loaded_macho.?) |*lm| lm else |err| err; + } + fn loadMachO(module: *const Module, gpa: Allocator) Error!LoadedMachO { + const all_mapped_memory = try mapDebugInfoFile(module.name); + errdefer posix.munmap(all_mapped_memory); + + // In most cases, the file we just mapped is a Mach-O binary. However, it could be a "universal + // binary": a simple file format which contains Mach-O binaries for multiple targets. For + // instance, `/usr/lib/dyld` is currently distributed as a universal binary containing images + // for both ARM64 macOS and x86_64 macOS. + if (all_mapped_memory.len < 4) return error.InvalidDebugInfo; + const magic = @as(*const u32, @ptrCast(all_mapped_memory.ptr)).*; + // The contents of a Mach-O file, which may or may not be the whole of `all_mapped_memory`. + const mapped_macho = switch (magic) { + macho.MH_MAGIC_64 => all_mapped_memory, + + macho.FAT_CIGAM => mapped_macho: { + // This is the universal binary format (aka a "fat binary"). Annoyingly, the whole thing + // is big-endian, so we'll be swapping some bytes. + if (all_mapped_memory.len < @sizeOf(macho.fat_header)) return error.InvalidDebugInfo; + const hdr: *const macho.fat_header = @ptrCast(all_mapped_memory.ptr); + const archs_ptr: [*]const macho.fat_arch = @ptrCast(all_mapped_memory.ptr + @sizeOf(macho.fat_header)); + const archs: []const macho.fat_arch = archs_ptr[0..@byteSwap(hdr.nfat_arch)]; + const native_cpu_type = switch (builtin.cpu.arch) { + .x86_64 => macho.CPU_TYPE_X86_64, + .aarch64 => macho.CPU_TYPE_ARM64, + else => comptime unreachable, + }; + for (archs) |*arch| { + if (@byteSwap(arch.cputype) != native_cpu_type) continue; + const offset = @byteSwap(arch.offset); + const size = @byteSwap(arch.size); + break :mapped_macho all_mapped_memory[offset..][0..size]; + } + // Our native architecture was not present in the fat binary. + return error.MissingDebugInfo; + }, + + // Even on modern 64-bit targets, this format doesn't seem to be too extensively used. It + // will be fairly easy to add support here if necessary; it's very similar to above. + macho.FAT_CIGAM_64 => return error.UnsupportedDebugInfo, + + else => return error.InvalidDebugInfo, + }; + + const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_macho.ptr)); + if (hdr.magic != macho.MH_MAGIC_64) + return error.InvalidDebugInfo; + + const symtab: macho.symtab_command, const text_vmaddr: u64 = lc_iter: { + var it: macho.LoadCommandIterator = .{ + .ncmds = hdr.ncmds, + .buffer = mapped_macho[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], + }; + var symtab: ?macho.symtab_command = null; + var text_vmaddr: ?u64 = null; + while (it.next()) |cmd| switch (cmd.cmd()) { + .SYMTAB => symtab = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo, + .SEGMENT_64 => if (cmd.cast(macho.segment_command_64)) |seg_cmd| { + if (!mem.eql(u8, seg_cmd.segName(), "__TEXT")) continue; + text_vmaddr = seg_cmd.vmaddr; + }, + else => {}, + }; + break :lc_iter .{ + symtab orelse return error.MissingDebugInfo, + text_vmaddr orelse return error.MissingDebugInfo, + }; + }; + + const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_macho[symtab.symoff..]); + const syms = syms_ptr[0..symtab.nsyms]; + const strings = mapped_macho[symtab.stroff..][0 .. symtab.strsize - 1]; + + var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len); + defer symbols.deinit(gpa); + + // This map is temporary; it is used only to detect duplicates here. This is + // necessary because we prefer to use STAB ("symbolic debugging table") symbols, + // but they might not be present, so we track normal symbols too. + // Indices match 1-1 with those of `symbols`. + var symbol_names: std.StringArrayHashMapUnmanaged(void) = .empty; + defer symbol_names.deinit(gpa); + try symbol_names.ensureUnusedCapacity(gpa, syms.len); + + var ofile: u32 = undefined; + var last_sym: MachoSymbol = undefined; + var state: enum { + init, + oso_open, + oso_close, + bnsym, + fun_strx, + fun_size, + ensym, + } = .init; + + for (syms) |*sym| { + if (sym.n_type.bits.is_stab == 0) { + if (sym.n_strx == 0) continue; + switch (sym.n_type.bits.type) { + .undf, .pbud, .indr, .abs, _ => continue, + .sect => { + const name = std.mem.sliceTo(strings[sym.n_strx..], 0); + const gop = symbol_names.getOrPutAssumeCapacity(name); + if (!gop.found_existing) { + assert(gop.index == symbols.items.len); + symbols.appendAssumeCapacity(.{ + .strx = sym.n_strx, + .addr = sym.n_value, + .ofile = MachoSymbol.unknown_ofile, + }); + } + }, + } + continue; + } + + // TODO handle globals N_GSYM, and statics N_STSYM + switch (sym.n_type.stab) { + .oso => switch (state) { + .init, .oso_close => { + state = .oso_open; + ofile = sym.n_strx; + }, + else => return error.InvalidDebugInfo, + }, + .bnsym => switch (state) { + .oso_open, .ensym => { + state = .bnsym; + last_sym = .{ + .strx = 0, + .addr = sym.n_value, + .ofile = ofile, + }; + }, + else => return error.InvalidDebugInfo, + }, + .fun => switch (state) { + .bnsym => { + state = .fun_strx; + last_sym.strx = sym.n_strx; + }, + .fun_strx => { + state = .fun_size; + }, + else => return error.InvalidDebugInfo, + }, + .ensym => switch (state) { + .fun_size => { + state = .ensym; + if (last_sym.strx != 0) { + const name = std.mem.sliceTo(strings[last_sym.strx..], 0); + const gop = symbol_names.getOrPutAssumeCapacity(name); + if (!gop.found_existing) { + assert(gop.index == symbols.items.len); + symbols.appendAssumeCapacity(last_sym); + } else { + symbols.items[gop.index] = last_sym; + } + } + }, + else => return error.InvalidDebugInfo, + }, + .so => switch (state) { + .init, .oso_close => {}, + .oso_open, .ensym => { + state = .oso_close; + }, + else => return error.InvalidDebugInfo, + }, + else => {}, + } + } + + switch (state) { + .init => { + // Missing STAB symtab entries is still okay, unless there were also no normal symbols. + if (symbols.items.len == 0) return error.MissingDebugInfo; + }, + .oso_close => {}, + else => return error.InvalidDebugInfo, // corrupted STAB entries in symtab + } + + const symbols_slice = try symbols.toOwnedSlice(gpa); + errdefer gpa.free(symbols_slice); + + // Even though lld emits symbols in ascending order, this debug code + // should work for programs linked in any valid way. + // This sort is so that we can binary search later. + mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan); + + return .{ + .mapped_memory = all_mapped_memory, + .symbols = symbols_slice, + .strings = strings, + .vaddr_offset = module.text_base - text_vmaddr, + }; + } +}; + +const OFile = struct { + mapped_memory: []align(std.heap.page_size_min) const u8, + dwarf: Dwarf, + strtab: []const u8, + symtab: []align(1) const macho.nlist_64, + /// All named symbols in `symtab`. Stored `u32` key is the index into `symtab`. Accessed + /// through `SymbolAdapter`, so that the symbol name is used as the logical key. + symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true), + + const SymbolAdapter = struct { + strtab: []const u8, + symtab: []align(1) const macho.nlist_64, + pub fn hash(ctx: SymbolAdapter, sym_name: []const u8) u32 { + _ = ctx; + return @truncate(std.hash.Wyhash.hash(0, sym_name)); + } + pub fn eql(ctx: SymbolAdapter, a_sym_name: []const u8, b_sym_index: u32, b_index: usize) bool { + _ = b_index; + const b_sym = ctx.symtab[b_sym_index]; + const b_sym_name = std.mem.sliceTo(ctx.strtab[b_sym.n_strx..], 0); + return mem.eql(u8, a_sym_name, b_sym_name); + } + }; +}; + +const MachoSymbol = struct { + strx: u32, + addr: u64, + /// Value may be `unknown_ofile`. + ofile: u32, + const unknown_ofile = std.math.maxInt(u32); + fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool { + _ = context; + return lhs.addr < rhs.addr; + } + /// Assumes that `symbols` is sorted in order of ascending `addr`. + fn find(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol { + if (symbols.len == 0) return null; // no potential match + if (address < symbols[0].addr) return null; // address is before the lowest-address symbol + var left: usize = 0; + var len: usize = symbols.len; + while (len > 1) { + const mid = left + len / 2; + if (address < symbols[mid].addr) { + len /= 2; + } else { + left = mid; + len -= len / 2; + } + } + return &symbols[left]; + } + + test find { + const symbols: []const MachoSymbol = &.{ + .{ .addr = 100, .strx = undefined, .ofile = undefined }, + .{ .addr = 200, .strx = undefined, .ofile = undefined }, + .{ .addr = 300, .strx = undefined, .ofile = undefined }, + }; + + try testing.expectEqual(null, find(symbols, 0)); + try testing.expectEqual(null, find(symbols, 99)); + try testing.expectEqual(&symbols[0], find(symbols, 100).?); + try testing.expectEqual(&symbols[0], find(symbols, 150).?); + try testing.expectEqual(&symbols[0], find(symbols, 199).?); + + try testing.expectEqual(&symbols[1], find(symbols, 200).?); + try testing.expectEqual(&symbols[1], find(symbols, 250).?); + try testing.expectEqual(&symbols[1], find(symbols, 299).?); + + try testing.expectEqual(&symbols[2], find(symbols, 300).?); + try testing.expectEqual(&symbols[2], find(symbols, 301).?); + try testing.expectEqual(&symbols[2], find(symbols, 5000).?); + } +}; +test { + _ = MachoSymbol; +} + +/// Uses `mmap` to map the file at `path` into memory. +fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 { + const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) { + error.FileNotFound => return error.MissingDebugInfo, + else => return error.ReadFailed, + }; + defer file.close(); + + const file_end_pos = file.getEndPos() catch |err| switch (err) { + error.Unexpected => |e| return e, + else => return error.ReadFailed, + }; + const file_len = std.math.cast(usize, file_end_pos) orelse return error.InvalidDebugInfo; + + return posix.mmap( + null, + file_len, + posix.PROT.READ, + .{ .TYPE = .SHARED }, + file.handle, + 0, + ) catch |err| switch (err) { + error.Unexpected => |e| return e, + else => return error.ReadFailed, + }; +} + +fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile { + const mapped_mem = try mapDebugInfoFile(o_file_path); + errdefer posix.munmap(mapped_mem); + + if (mapped_mem.len < @sizeOf(macho.mach_header_64)) return error.InvalidDebugInfo; + const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr)); + if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidDebugInfo; + + const seg_cmd: macho.LoadCommandIterator.LoadCommand, const symtab_cmd: macho.symtab_command = cmds: { + var seg_cmd: ?macho.LoadCommandIterator.LoadCommand = null; + var symtab_cmd: ?macho.symtab_command = null; + var it: macho.LoadCommandIterator = .{ + .ncmds = hdr.ncmds, + .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], + }; + while (it.next()) |cmd| switch (cmd.cmd()) { + .SEGMENT_64 => seg_cmd = cmd, + .SYMTAB => symtab_cmd = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo, + else => {}, + }; + break :cmds .{ + seg_cmd orelse return error.MissingDebugInfo, + symtab_cmd orelse return error.MissingDebugInfo, + }; + }; + + if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidDebugInfo; + if (mapped_mem[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidDebugInfo; + const strtab = mapped_mem[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1]; + + const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64); + if (mapped_mem.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidDebugInfo; + const symtab: []align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab_cmd.symoff..][0..n_sym_bytes]); + + // TODO handle tentative (common) symbols + var symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true) = .empty; + defer symbols_by_name.deinit(gpa); + try symbols_by_name.ensureUnusedCapacity(gpa, @intCast(symtab.len)); + for (symtab, 0..) |sym, sym_index| { + if (sym.n_strx == 0) continue; + switch (sym.n_type.bits.type) { + .undf => continue, // includes tentative symbols + .abs => continue, + else => {}, + } + const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0); + const gop = symbols_by_name.getOrPutAssumeCapacityAdapted( + @as([]const u8, sym_name), + @as(OFile.SymbolAdapter, .{ .strtab = strtab, .symtab = symtab }), + ); + if (gop.found_existing) return error.InvalidDebugInfo; + gop.key_ptr.* = @intCast(sym_index); + } + + var sections: Dwarf.SectionArray = @splat(null); + for (seg_cmd.getSections()) |sect| { + if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue; + + const section_index: usize = inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| { + if (mem.eql(u8, "__" ++ section.name, sect.sectName())) break i; + } else continue; + + if (mapped_mem.len < sect.offset + sect.size) return error.InvalidDebugInfo; + const section_bytes = mapped_mem[sect.offset..][0..sect.size]; + sections[section_index] = .{ + .data = section_bytes, + .owned = false, + }; + } + + const missing_debug_info = + sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or + sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or + sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or + sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; + if (missing_debug_info) return error.MissingDebugInfo; + + var dwarf: Dwarf = .{ .sections = sections }; + errdefer dwarf.deinit(gpa); + try dwarf.open(gpa, native_endian); + + return .{ + .mapped_memory = mapped_mem, + .dwarf = dwarf, + .strtab = strtab, + .symtab = symtab, + .symbols_by_name = symbols_by_name.move(), + }; +} + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const Dwarf = std.debug.Dwarf; +const Error = std.debug.SelfInfoError; +const assert = std.debug.assert; +const posix = std.posix; +const macho = std.macho; +const mem = std.mem; +const testing = std.testing; +const dwarfRegNative = std.debug.Dwarf.SelfUnwinder.regNative; + +const builtin = @import("builtin"); +const native_endian = builtin.target.cpu.arch.endian(); + +const SelfInfo = @This(); diff --git a/lib/std/debug/SelfInfo/Windows.zig b/lib/std/debug/SelfInfo/Windows.zig new file mode 100644 index 0000000000..f84836a6d4 --- /dev/null +++ b/lib/std/debug/SelfInfo/Windows.zig @@ -0,0 +1,559 @@ +mutex: std.Thread.Mutex, +modules: std.ArrayListUnmanaged(Module), +module_name_arena: std.heap.ArenaAllocator.State, + +pub const init: SelfInfo = .{ + .mutex = .{}, + .modules = .empty, + .module_name_arena = .{}, +}; +pub fn deinit(si: *SelfInfo, gpa: Allocator) void { + for (si.modules.items) |*module| { + di: { + const di = &(module.di orelse break :di catch break :di); + di.deinit(gpa); + } + } + si.modules.deinit(gpa); + + var module_name_arena = si.module_name_arena.promote(gpa); + module_name_arena.deinit(); +} + +pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol { + si.mutex.lock(); + defer si.mutex.unlock(); + const module = try si.findModule(gpa, address); + const di = try module.getDebugInfo(gpa); + return di.getSymbol(gpa, address - module.base_address); +} +pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 { + si.mutex.lock(); + defer si.mutex.unlock(); + const module = try si.findModule(gpa, address); + return module.name; +} + +pub const can_unwind: bool = switch (builtin.cpu.arch) { + else => true, + // On x86, `RtlVirtualUnwind` does not exist. We could in theory use `RtlCaptureStackBackTrace` + // instead, but on x86, it turns out that function is just... doing FP unwinding with esp! It's + // hard to find implementation details to confirm that, but the most authoritative source I have + // is an entry in the LLVM mailing list from 2020/08/16 which contains this quote: + // + // > x86 doesn't have what most architectures would consider an "unwinder" in the sense of + // > restoring registers; there is simply a linked list of frames that participate in SEH and + // > that desire to be called for a dynamic unwind operation, so RtlCaptureStackBackTrace + // > assumes that EBP-based frames are in use and walks an EBP-based frame chain on x86 - not + // > all x86 code is written with EBP-based frames so while even though we generally build the + // > OS that way, you might always run the risk of encountering external code that uses EBP as a + // > general purpose register for which such an unwind attempt for a stack trace would fail. + // + // Regardless, it's easy to effectively confirm this hypothesis just by compiling some code with + // `-fomit-frame-pointer -OReleaseFast` and observing that `RtlCaptureStackBackTrace` returns an + // empty trace when it's called in such an application. Note that without `-OReleaseFast` or + // similar, LLVM seems reluctant to ever clobber ebp, so you'll get a trace returned which just + // contains all of the kernel32/ntdll frames but none of your own. Don't be deceived---this is + // just coincidental! + // + // Anyway, the point is, the only stack walking primitive on x86-windows is FP unwinding. We + // *could* ask Microsoft to do that for us with `RtlCaptureStackBackTrace`... but better to just + // use our existing FP unwinder in `std.debug`! + .x86 => false, +}; +pub const UnwindContext = struct { + pc: usize, + cur: windows.CONTEXT, + history_table: windows.UNWIND_HISTORY_TABLE, + pub fn init(ctx: *const std.debug.cpu_context.Native) UnwindContext { + return .{ + .pc = @returnAddress(), + .cur = switch (builtin.cpu.arch) { + .x86_64 => std.mem.zeroInit(windows.CONTEXT, .{ + .Rax = ctx.gprs.get(.rax), + .Rcx = ctx.gprs.get(.rcx), + .Rdx = ctx.gprs.get(.rdx), + .Rbx = ctx.gprs.get(.rbx), + .Rsp = ctx.gprs.get(.rsp), + .Rbp = ctx.gprs.get(.rbp), + .Rsi = ctx.gprs.get(.rsi), + .Rdi = ctx.gprs.get(.rdi), + .R8 = ctx.gprs.get(.r8), + .R9 = ctx.gprs.get(.r9), + .R10 = ctx.gprs.get(.r10), + .R11 = ctx.gprs.get(.r11), + .R12 = ctx.gprs.get(.r12), + .R13 = ctx.gprs.get(.r13), + .R14 = ctx.gprs.get(.r14), + .R15 = ctx.gprs.get(.r15), + .Rip = ctx.gprs.get(.rip), + }), + .aarch64 => .{ + .ContextFlags = 0, + .Cpsr = 0, + .DUMMYUNIONNAME = .{ .X = ctx.x }, + .Sp = ctx.sp, + .Pc = ctx.pc, + .V = @splat(.{ .B = @splat(0) }), + .Fpcr = 0, + .Fpsr = 0, + .Bcr = @splat(0), + .Bvr = @splat(0), + .Wcr = @splat(0), + .Wvr = @splat(0), + }, + .thumb => .{ + .ContextFlags = 0, + .R0 = ctx.r[0], + .R1 = ctx.r[1], + .R2 = ctx.r[2], + .R3 = ctx.r[3], + .R4 = ctx.r[4], + .R5 = ctx.r[5], + .R6 = ctx.r[6], + .R7 = ctx.r[7], + .R8 = ctx.r[8], + .R9 = ctx.r[9], + .R10 = ctx.r[10], + .R11 = ctx.r[11], + .R12 = ctx.r[12], + .Sp = ctx.r[13], + .Lr = ctx.r[14], + .Pc = ctx.r[15], + .Cpsr = 0, + .Fpcsr = 0, + .Padding = 0, + .DUMMYUNIONNAME = .{ .S = @splat(0) }, + .Bvr = @splat(0), + .Bcr = @splat(0), + .Wvr = @splat(0), + .Wcr = @splat(0), + .Padding2 = @splat(0), + }, + else => comptime unreachable, + }, + .history_table = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE), + }; + } + pub fn deinit(ctx: *UnwindContext, gpa: Allocator) void { + _ = ctx; + _ = gpa; + } + pub fn getFp(ctx: *UnwindContext) usize { + return ctx.cur.getRegs().bp; + } +}; +pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize { + _ = si; + _ = gpa; + + const current_regs = context.cur.getRegs(); + var image_base: windows.DWORD64 = undefined; + if (windows.ntdll.RtlLookupFunctionEntry(current_regs.ip, &image_base, &context.history_table)) |runtime_function| { + var handler_data: ?*anyopaque = null; + var establisher_frame: u64 = undefined; + _ = windows.ntdll.RtlVirtualUnwind( + windows.UNW_FLAG_NHANDLER, + image_base, + current_regs.ip, + runtime_function, + &context.cur, + &handler_data, + &establisher_frame, + null, + ); + } else { + // leaf function + context.cur.setIp(@as(*const usize, @ptrFromInt(current_regs.sp)).*); + context.cur.setSp(current_regs.sp + @sizeOf(usize)); + } + + const next_regs = context.cur.getRegs(); + const tib = &windows.teb().NtTib; + if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) { + context.pc = 0; + return 0; + } + // Like `DwarfUnwindContext.unwindFrame`, adjust our next lookup pc in case the `call` was this + // function's last instruction making `next_regs.ip` one byte past its end. + context.pc = next_regs.ip -| 1; + return next_regs.ip; +} + +const Module = struct { + base_address: usize, + size: u32, + name: []const u8, + handle: windows.HMODULE, + + di: ?(Error!DebugInfo), + + const DebugInfo = struct { + arena: std.heap.ArenaAllocator.State, + coff_image_base: u64, + mapped_file: ?MappedFile, + dwarf: ?Dwarf, + pdb: ?Pdb, + coff_section_headers: []coff.SectionHeader, + + const MappedFile = struct { + file: fs.File, + section_handle: windows.HANDLE, + section_view: []const u8, + fn deinit(mf: *const MappedFile) void { + const process_handle = windows.GetCurrentProcess(); + assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(mf.section_view.ptr)) == .SUCCESS); + windows.CloseHandle(mf.section_handle); + mf.file.close(); + } + }; + + fn deinit(di: *DebugInfo, gpa: Allocator) void { + if (di.dwarf) |*dwarf| dwarf.deinit(gpa); + if (di.pdb) |*pdb| { + pdb.file_reader.file.close(); + pdb.deinit(); + } + if (di.mapped_file) |*mf| mf.deinit(); + + var arena = di.arena.promote(gpa); + arena.deinit(); + } + + fn getSymbol(di: *DebugInfo, gpa: Allocator, vaddr: usize) Error!std.debug.Symbol { + pdb: { + const pdb = &(di.pdb orelse break :pdb); + var coff_section: *align(1) const coff.SectionHeader = undefined; + const mod_index = for (pdb.sect_contribs) |sect_contrib| { + if (sect_contrib.section > di.coff_section_headers.len) continue; + // Remember that SectionContribEntry.Section is 1-based. + coff_section = &di.coff_section_headers[sect_contrib.section - 1]; + + const vaddr_start = coff_section.virtual_address + sect_contrib.offset; + const vaddr_end = vaddr_start + sect_contrib.size; + if (vaddr >= vaddr_start and vaddr < vaddr_end) { + break sect_contrib.module_index; + } + } else { + // we have no information to add to the address + break :pdb; + }; + const module = pdb.getModule(mod_index) catch |err| switch (err) { + error.InvalidDebugInfo, + error.MissingDebugInfo, + error.OutOfMemory, + => |e| return e, + + error.ReadFailed, + error.EndOfStream, + => return error.InvalidDebugInfo, + } orelse { + return error.InvalidDebugInfo; // bad module index + }; + return .{ + .name = pdb.getSymbolName(module, vaddr - coff_section.virtual_address), + .compile_unit_name = fs.path.basename(module.obj_file_name), + .source_location = pdb.getLineNumberInfo(module, vaddr - coff_section.virtual_address) catch null, + }; + } + dwarf: { + const dwarf = &(di.dwarf orelse break :dwarf); + const dwarf_address = vaddr + di.coff_image_base; + return dwarf.getSymbol(gpa, native_endian, dwarf_address) catch |err| switch (err) { + error.MissingDebugInfo => break :dwarf, + + error.InvalidDebugInfo, + error.OutOfMemory, + => |e| return e, + + error.ReadFailed, + error.EndOfStream, + error.Overflow, + error.StreamTooLong, + => return error.InvalidDebugInfo, + }; + } + return error.MissingDebugInfo; + } + }; + + fn getDebugInfo(module: *Module, gpa: Allocator) Error!*DebugInfo { + if (module.di == null) module.di = loadDebugInfo(module, gpa); + return if (module.di.?) |*di| di else |err| err; + } + fn loadDebugInfo(module: *const Module, gpa: Allocator) Error!DebugInfo { + const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address); + const mapped = mapped_ptr[0..module.size]; + var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo; + + var arena_instance: std.heap.ArenaAllocator = .init(gpa); + errdefer arena_instance.deinit(); + const arena = arena_instance.allocator(); + + // The string table is not mapped into memory by the loader, so if a section name is in the + // string table then we have to map the full image file from disk. This can happen when + // a binary is produced with -gdwarf, since the section names are longer than 8 bytes. + const mapped_file: ?DebugInfo.MappedFile = mapped: { + if (!coff_obj.strtabRequired()) break :mapped null; + var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined; + name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present + const process_handle = windows.GetCurrentProcess(); + const len = windows.kernel32.GetModuleFileNameExW( + process_handle, + module.handle, + name_buffer[4..], + windows.PATH_MAX_WIDE, + ); + if (len == 0) return error.MissingDebugInfo; + const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) { + error.Unexpected => |e| return e, + error.FileNotFound => return error.MissingDebugInfo, + + error.FileTooBig, + error.IsDir, + error.NotDir, + error.SymLinkLoop, + error.NameTooLong, + error.InvalidUtf8, + error.InvalidWtf8, + error.BadPathName, + => return error.InvalidDebugInfo, + + error.SystemResources, + error.WouldBlock, + error.AccessDenied, + error.ProcessNotFound, + error.PermissionDenied, + error.NoSpaceLeft, + error.DeviceBusy, + error.NoDevice, + error.SharingViolation, + error.PathAlreadyExists, + error.PipeBusy, + error.NetworkNotFound, + error.AntivirusInterference, + error.ProcessFdQuotaExceeded, + error.SystemFdQuotaExceeded, + error.FileLocksNotSupported, + error.FileBusy, + => return error.ReadFailed, + }; + errdefer coff_file.close(); + var section_handle: windows.HANDLE = undefined; + const create_section_rc = windows.ntdll.NtCreateSection( + §ion_handle, + windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ, + null, + null, + windows.PAGE_READONLY, + // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default. + // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6. + windows.SEC_COMMIT, + coff_file.handle, + ); + if (create_section_rc != .SUCCESS) return error.MissingDebugInfo; + errdefer windows.CloseHandle(section_handle); + var coff_len: usize = 0; + var section_view_ptr: ?[*]const u8 = null; + const map_section_rc = windows.ntdll.NtMapViewOfSection( + section_handle, + process_handle, + @ptrCast(§ion_view_ptr), + null, + 0, + null, + &coff_len, + .ViewUnmap, + 0, + windows.PAGE_READONLY, + ); + if (map_section_rc != .SUCCESS) return error.MissingDebugInfo; + errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr.?)) == .SUCCESS); + const section_view = section_view_ptr.?[0..coff_len]; + coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo; + break :mapped .{ + .file = coff_file, + .section_handle = section_handle, + .section_view = section_view, + }; + }; + errdefer if (mapped_file) |*mf| mf.deinit(); + + const coff_image_base = coff_obj.getImageBase(); + + var opt_dwarf: ?Dwarf = dwarf: { + if (coff_obj.getSectionByName(".debug_info") == null) break :dwarf null; + + var sections: Dwarf.SectionArray = undefined; + inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| { + sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| .{ + .data = try coff_obj.getSectionDataAlloc(section_header, arena), + .owned = false, + } else null; + } + break :dwarf .{ .sections = sections }; + }; + errdefer if (opt_dwarf) |*dwarf| dwarf.deinit(gpa); + + if (opt_dwarf) |*dwarf| { + dwarf.open(gpa, native_endian) catch |err| switch (err) { + error.Overflow, + error.EndOfStream, + error.StreamTooLong, + error.ReadFailed, + => return error.InvalidDebugInfo, + + error.InvalidDebugInfo, + error.MissingDebugInfo, + error.OutOfMemory, + => |e| return e, + }; + } + + var opt_pdb: ?Pdb = pdb: { + const path = coff_obj.getPdbPath() catch { + return error.InvalidDebugInfo; + } orelse { + break :pdb null; + }; + const pdb_file_open_result = if (fs.path.isAbsolute(path)) res: { + break :res std.fs.cwd().openFile(path, .{}); + } else res: { + const self_dir = fs.selfExeDirPathAlloc(gpa) catch |err| switch (err) { + error.OutOfMemory, error.Unexpected => |e| return e, + else => return error.ReadFailed, + }; + defer gpa.free(self_dir); + const abs_path = try fs.path.join(gpa, &.{ self_dir, path }); + defer gpa.free(abs_path); + break :res std.fs.cwd().openFile(abs_path, .{}); + }; + const pdb_file = pdb_file_open_result catch |err| switch (err) { + error.FileNotFound, error.IsDir => break :pdb null, + else => return error.ReadFailed, + }; + errdefer pdb_file.close(); + + const pdb_reader = try arena.create(std.fs.File.Reader); + pdb_reader.* = pdb_file.reader(try arena.alloc(u8, 4096)); + + var pdb = Pdb.init(gpa, pdb_reader) catch |err| switch (err) { + error.OutOfMemory, error.ReadFailed, error.Unexpected => |e| return e, + else => return error.InvalidDebugInfo, + }; + errdefer pdb.deinit(); + pdb.parseInfoStream() catch |err| switch (err) { + error.UnknownPDBVersion => return error.UnsupportedDebugInfo, + error.EndOfStream => return error.InvalidDebugInfo, + + error.InvalidDebugInfo, + error.MissingDebugInfo, + error.OutOfMemory, + error.ReadFailed, + => |e| return e, + }; + pdb.parseDbiStream() catch |err| switch (err) { + error.UnknownPDBVersion => return error.UnsupportedDebugInfo, + + error.EndOfStream, + error.EOF, + error.StreamTooLong, + error.WriteFailed, + => return error.InvalidDebugInfo, + + error.InvalidDebugInfo, + error.OutOfMemory, + error.ReadFailed, + => |e| return e, + }; + + if (!std.mem.eql(u8, &coff_obj.guid, &pdb.guid) or coff_obj.age != pdb.age) + return error.InvalidDebugInfo; + + break :pdb pdb; + }; + errdefer if (opt_pdb) |*pdb| { + pdb.file_reader.file.close(); + pdb.deinit(); + }; + + const coff_section_headers: []coff.SectionHeader = if (opt_pdb != null) csh: { + break :csh try coff_obj.getSectionHeadersAlloc(arena); + } else &.{}; + + return .{ + .arena = arena_instance.state, + .coff_image_base = coff_image_base, + .mapped_file = mapped_file, + .dwarf = opt_dwarf, + .pdb = opt_pdb, + .coff_section_headers = coff_section_headers, + }; + } +}; + +/// Assumes we already hold `si.mutex`. +fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) error{ MissingDebugInfo, OutOfMemory, Unexpected }!*Module { + for (si.modules.items) |*mod| { + if (address >= mod.base_address and address < mod.base_address + mod.size) { + return mod; + } + } + + // A new module might have been loaded; rebuild the list. + { + for (si.modules.items) |*mod| { + const di = &(mod.di orelse continue catch continue); + di.deinit(gpa); + } + si.modules.clearRetainingCapacity(); + + var module_name_arena = si.module_name_arena.promote(gpa); + defer si.module_name_arena = module_name_arena.state; + _ = module_name_arena.reset(.retain_capacity); + + const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0); + if (handle == windows.INVALID_HANDLE_VALUE) { + return windows.unexpectedError(windows.GetLastError()); + } + defer windows.CloseHandle(handle); + var entry: windows.MODULEENTRY32 = undefined; + entry.dwSize = @sizeOf(windows.MODULEENTRY32); + var result = windows.kernel32.Module32First(handle, &entry); + while (result != 0) : (result = windows.kernel32.Module32Next(handle, &entry)) { + try si.modules.append(gpa, .{ + .base_address = @intFromPtr(entry.modBaseAddr), + .size = entry.modBaseSize, + .name = try module_name_arena.allocator().dupe( + u8, + std.mem.sliceTo(&entry.szModule, 0), + ), + .handle = entry.hModule, + .di = null, + }); + } + } + + for (si.modules.items) |*mod| { + if (address >= mod.base_address and address < mod.base_address + mod.size) { + return mod; + } + } + + return error.MissingDebugInfo; +} + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const Dwarf = std.debug.Dwarf; +const Pdb = std.debug.Pdb; +const Error = std.debug.SelfInfoError; +const assert = std.debug.assert; +const coff = std.coff; +const fs = std.fs; +const windows = std.os.windows; + +const builtin = @import("builtin"); +const native_endian = builtin.target.cpu.arch.endian(); + +const SelfInfo = @This(); diff --git a/lib/std/debug/cpu_context.zig b/lib/std/debug/cpu_context.zig new file mode 100644 index 0000000000..dc77b41c9d --- /dev/null +++ b/lib/std/debug/cpu_context.zig @@ -0,0 +1,1888 @@ +/// Register state for the native architecture, used by `std.debug` for stack unwinding. +/// `noreturn` if there is no implementation for the native architecture. +/// This can be overriden by exposing a declaration `root.debug.CpuContext`. +pub const Native = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "CpuContext")) + root.debug.CpuContext +else switch (native_arch) { + .aarch64, .aarch64_be => Aarch64, + .arm, .armeb, .thumb, .thumbeb => Arm, + .hexagon => Hexagon, + .loongarch32, .loongarch64 => LoongArch, + .mips, .mipsel, .mips64, .mips64el => Mips, + .powerpc, .powerpcle, .powerpc64, .powerpc64le => Powerpc, + .riscv32, .riscv32be, .riscv64, .riscv64be => Riscv, + .s390x => S390x, + .x86 => X86, + .x86_64 => X86_64, + else => noreturn, +}; + +pub const DwarfRegisterError = error{ + InvalidRegister, + UnsupportedRegister, +}; + +pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native { + if (signal_ucontext_t == void) return null; + + // In general, we include the hardwired zero register in the context if applicable. + const uc: *const signal_ucontext_t = @ptrCast(@alignCast(ctx_ptr)); + + // Deal with some special cases first. + if (native_arch.isMIPS32() and native_os == .linux) { + // The O32 kABI uses 64-bit fields for some reason. + return .{ + .r = s: { + var regs: [32]Mips.Gpr = undefined; + for (uc.mcontext.r, 0..) |r, i| regs[i] = @truncate(r); + break :s regs; + }, + .pc = @truncate(uc.mcontext.pc), + }; + } + + // Only unified conversions from here. + return switch (native_arch) { + .arm, .armeb, .thumb, .thumbeb => .{ + .r = uc.mcontext.r ++ [_]u32{uc.mcontext.pc}, + }, + .aarch64, .aarch64_be => .{ + .x = uc.mcontext.x ++ [_]u64{uc.mcontext.lr}, + .sp = uc.mcontext.sp, + .pc = uc.mcontext.pc, + }, + .hexagon, .loongarch32, .loongarch64, .mips, .mipsel, .mips64, .mips64el, .or1k => .{ + .r = uc.mcontext.r, + .pc = uc.mcontext.pc, + }, + .powerpc, .powerpcle, .powerpc64, .powerpc64le => .{ + .r = uc.mcontext.r, + .pc = uc.mcontext.pc, + .lr = uc.mcontext.lr, + }, + .riscv32, .riscv32be, .riscv64, .riscv64be => .{ + // You can thank FreeBSD and OpenBSD for this silliness; they decided to be cute and + // group the registers by ABI mnemonic rather than register number. + .x = [_]Riscv.Gpr{0} ++ + uc.mcontext.ra_sp_gp_tp ++ + uc.mcontext.t0_2 ++ + uc.mcontext.s0_1 ++ + uc.mcontext.a ++ + uc.mcontext.s2_11 ++ + uc.mcontext.t3_6, + .pc = uc.mcontext.pc, + }, + .s390x => .{ + .r = uc.mcontext.r, + .psw = .{ + .mask = uc.mcontext.psw.mask, + .addr = uc.mcontext.psw.addr, + }, + }, + .x86 => .{ .gprs = .init(.{ + .eax = uc.mcontext.eax, + .ecx = uc.mcontext.ecx, + .edx = uc.mcontext.edx, + .ebx = uc.mcontext.ebx, + .esp = uc.mcontext.esp, + .ebp = uc.mcontext.ebp, + .esi = uc.mcontext.esi, + .edi = uc.mcontext.edi, + .eip = uc.mcontext.eip, + }) }, + .x86_64 => .{ .gprs = .init(.{ + .rax = uc.mcontext.rax, + .rdx = uc.mcontext.rdx, + .rcx = uc.mcontext.rcx, + .rbx = uc.mcontext.rbx, + .rsi = uc.mcontext.rsi, + .rdi = uc.mcontext.rdi, + .rbp = uc.mcontext.rbp, + .rsp = uc.mcontext.rsp, + .r8 = uc.mcontext.r8, + .r9 = uc.mcontext.r9, + .r10 = uc.mcontext.r10, + .r11 = uc.mcontext.r11, + .r12 = uc.mcontext.r12, + .r13 = uc.mcontext.r13, + .r14 = uc.mcontext.r14, + .r15 = uc.mcontext.r15, + .rip = uc.mcontext.rip, + }) }, + else => comptime unreachable, + }; +} + +pub fn fromWindowsContext(ctx: *const std.os.windows.CONTEXT) Native { + return switch (native_arch) { + .x86 => .{ .gprs = .init(.{ + .eax = ctx.Eax, + .ecx = ctx.Ecx, + .edx = ctx.Edx, + .ebx = ctx.Ebx, + .esp = ctx.Esp, + .ebp = ctx.Ebp, + .esi = ctx.Esi, + .edi = ctx.Edi, + .eip = ctx.Eip, + }) }, + .x86_64 => .{ .gprs = .init(.{ + .rax = ctx.Rax, + .rdx = ctx.Rdx, + .rcx = ctx.Rcx, + .rbx = ctx.Rbx, + .rsi = ctx.Rsi, + .rdi = ctx.Rdi, + .rbp = ctx.Rbp, + .rsp = ctx.Rsp, + .r8 = ctx.R8, + .r9 = ctx.R9, + .r10 = ctx.R10, + .r11 = ctx.R11, + .r12 = ctx.R12, + .r13 = ctx.R13, + .r14 = ctx.R14, + .r15 = ctx.R15, + .rip = ctx.Rip, + }) }, + .aarch64 => .{ + .x = ctx.DUMMYUNIONNAME.X[0..31].*, + .sp = ctx.Sp, + .pc = ctx.Pc, + }, + .thumb => .{ .r = .{ + ctx.R0, ctx.R1, ctx.R2, ctx.R3, + ctx.R4, ctx.R5, ctx.R6, ctx.R7, + ctx.R8, ctx.R9, ctx.R10, ctx.R11, + ctx.R12, ctx.Sp, ctx.Lr, ctx.Pc, + } }, + else => comptime unreachable, + }; +} + +const X86 = struct { + /// The first 8 registers here intentionally match the order of registers in the x86 instruction + /// encoding. This order is inherited by the PUSHA instruction and the DWARF register mappings, + /// among other things. + pub const Gpr = enum { + // zig fmt: off + eax, ecx, edx, ebx, + esp, ebp, esi, edi, + eip, + // zig fmt: on + }; + gprs: std.enums.EnumArray(Gpr, u32), + + pub inline fn current() X86 { + var ctx: X86 = undefined; + asm volatile ( + \\movl %%eax, 0x00(%%edi) + \\movl %%ecx, 0x04(%%edi) + \\movl %%edx, 0x08(%%edi) + \\movl %%ebx, 0x0c(%%edi) + \\movl %%esp, 0x10(%%edi) + \\movl %%ebp, 0x14(%%edi) + \\movl %%esi, 0x18(%%edi) + \\movl %%edi, 0x1c(%%edi) + \\call 1f + \\1: + \\popl 0x20(%%edi) + : + : [gprs] "{edi}" (&ctx.gprs.values), + : .{ .memory = true }); + return ctx; + } + + pub fn dwarfRegisterBytes(ctx: *X86, register_num: u16) DwarfRegisterError![]u8 { + // System V Application Binary Interface Intel386 Architecture Processor Supplement Version 1.1 + // § 2.4.2 "DWARF Register Number Mapping" + switch (register_num) { + // The order of `Gpr` intentionally matches DWARF's mappings. + // + // x86-macos sometimes uses different mappings (ebp and esp are reversed when the unwind + // information is from `__eh_frame`). This deviation is not considered here, because + // x86-macos is a deprecated target which is not supported by the Zig Standard Library. + 0...8 => return @ptrCast(&ctx.gprs.values[register_num]), + + 9 => return error.UnsupportedRegister, // eflags + 11...18 => return error.UnsupportedRegister, // st0 - st7 + 21...28 => return error.UnsupportedRegister, // xmm0 - xmm7 + 29...36 => return error.UnsupportedRegister, // mm0 - mm7 + 39 => return error.UnsupportedRegister, // mxcsr + 40...45 => return error.UnsupportedRegister, // es, cs, ss, ds, fs, gs + 48 => return error.UnsupportedRegister, // tr + 49 => return error.UnsupportedRegister, // ldtr + 93...100 => return error.UnsupportedRegister, // k0 - k7 (AVX-512) + + else => return error.InvalidRegister, + } + } +}; + +const X86_64 = struct { + /// The order here intentionally matches the order of the DWARF register mappings. It's unclear + /// where those mappings actually originated from---the ordering of the first 4 registers seems + /// quite unusual---but it is currently convenient for us to match DWARF. + pub const Gpr = enum { + // zig fmt: off + rax, rdx, rcx, rbx, + rsi, rdi, rbp, rsp, + r8, r9, r10, r11, + r12, r13, r14, r15, + rip, + // zig fmt: on + }; + gprs: std.enums.EnumArray(Gpr, u64), + + pub inline fn current() X86_64 { + var ctx: X86_64 = undefined; + asm volatile ( + \\movq %%rax, 0x00(%%rdi) + \\movq %%rdx, 0x08(%%rdi) + \\movq %%rcx, 0x10(%%rdi) + \\movq %%rbx, 0x18(%%rdi) + \\movq %%rsi, 0x20(%%rdi) + \\movq %%rdi, 0x28(%%rdi) + \\movq %%rbp, 0x30(%%rdi) + \\movq %%rsp, 0x38(%%rdi) + \\movq %%r8, 0x40(%%rdi) + \\movq %%r9, 0x48(%%rdi) + \\movq %%r10, 0x50(%%rdi) + \\movq %%r11, 0x58(%%rdi) + \\movq %%r12, 0x60(%%rdi) + \\movq %%r13, 0x68(%%rdi) + \\movq %%r14, 0x70(%%rdi) + \\movq %%r15, 0x78(%%rdi) + \\leaq (%%rip), %%rax + \\movq %%rax, 0x80(%%rdi) + \\movq 0x00(%%rdi), %%rax + : + : [gprs] "{rdi}" (&ctx.gprs.values), + : .{ .memory = true }); + return ctx; + } + + pub fn dwarfRegisterBytes(ctx: *X86_64, register_num: u16) DwarfRegisterError![]u8 { + // System V Application Binary Interface AMD64 Architecture Processor Supplement + // § 3.6.2 "DWARF Register Number Mapping" + switch (register_num) { + // The order of `Gpr` intentionally matches DWARF's mappings. + 0...16 => return @ptrCast(&ctx.gprs.values[register_num]), + + 17...32 => return error.UnsupportedRegister, // xmm0 - xmm15 + 33...40 => return error.UnsupportedRegister, // st0 - st7 + 41...48 => return error.UnsupportedRegister, // mm0 - mm7 + 49 => return error.UnsupportedRegister, // rflags + 50...55 => return error.UnsupportedRegister, // es, cs, ss, ds, fs, gs + 58...59 => return error.UnsupportedRegister, // fs.base, gs.base + 62 => return error.UnsupportedRegister, // tr + 63 => return error.UnsupportedRegister, // ldtr + 64 => return error.UnsupportedRegister, // mxcsr + 65 => return error.UnsupportedRegister, // fcw + 66 => return error.UnsupportedRegister, // fsw + 67...82 => return error.UnsupportedRegister, // xmm16 - xmm31 (AVX-512) + 118...125 => return error.UnsupportedRegister, // k0 - k7 (AVX-512) + 130...145 => return error.UnsupportedRegister, // r16 - r31 (APX) + + else => return error.InvalidRegister, + } + } +}; + +const Arm = struct { + /// The numbered general-purpose registers R0 - R15. + r: [16]u32, + + pub inline fn current() Arm { + var ctx: Arm = undefined; + asm volatile ( + \\// For compatibility with Thumb, we can't write r13 (sp) or r15 (pc) with stm. + \\stm r0, {r0-r12} + \\str r13, [r0, #0x34] + \\str r14, [r0, #0x38] + \\str r15, [r0, #0x3c] + : + : [r] "{r0}" (&ctx.r), + : .{ .memory = true }); + return ctx; + } + + pub fn dwarfRegisterBytes(ctx: *Arm, register_num: u16) DwarfRegisterError![]u8 { + // DWARF for the Arm(r) Architecture § 4.1 "DWARF register names" + switch (register_num) { + 0...15 => return @ptrCast(&ctx.r[register_num]), + + 64...95 => return error.UnsupportedRegister, // S0 - S31 + 96...103 => return error.UnsupportedRegister, // F0 - F7 + 104...111 => return error.UnsupportedRegister, // wCGR0 - wCGR7, or ACC0 - ACC7 + 112...127 => return error.UnsupportedRegister, // wR0 - wR15 + 128 => return error.UnsupportedRegister, // SPSR + 129 => return error.UnsupportedRegister, // SPSR_FIQ + 130 => return error.UnsupportedRegister, // SPSR_IRQ + 131 => return error.UnsupportedRegister, // SPSR_ABT + 132 => return error.UnsupportedRegister, // SPSR_UND + 133 => return error.UnsupportedRegister, // SPSR_SVC + 134...142 => return error.UnsupportedRegister, // Reserved + 143 => return error.UnsupportedRegister, // RA_AUTH_CODE + 144...150 => return error.UnsupportedRegister, // R8_USR - R14_USR + 151...157 => return error.UnsupportedRegister, // R8_FIQ - R14_FIQ + 158...159 => return error.UnsupportedRegister, // R13_IRQ - R14_IRQ + 160...161 => return error.UnsupportedRegister, // R13_ABT - R14_ABT + 162...163 => return error.UnsupportedRegister, // R13_UND - R14_UND + 164...165 => return error.UnsupportedRegister, // R13_SVC - R14_SVC + 166...191 => return error.UnsupportedRegister, // Reserved + 192...199 => return error.UnsupportedRegister, // wC0 - wC7 + 200...255 => return error.UnsupportedRegister, // Reserved + 256...287 => return error.UnsupportedRegister, // D0 - D31 + 288...319 => return error.UnsupportedRegister, // Reserved for FP/NEON + 320 => return error.UnsupportedRegister, // TPIDRURO + 321 => return error.UnsupportedRegister, // TPIDRURW + 322 => return error.UnsupportedRegister, // TPIDPR + 323 => return error.UnsupportedRegister, // HTPIDPR + 324...8191 => return error.UnsupportedRegister, // Reserved + 8192...16383 => return error.UnsupportedRegister, // Unspecified vendor co-processor register + + else => return error.InvalidRegister, + } + } +}; + +/// This is an `extern struct` so that inline assembly in `current` can use field offsets. +const Aarch64 = extern struct { + /// The numbered general-purpose registers X0 - X30. + x: [31]u64, + sp: u64, + pc: u64, + + pub inline fn current() Aarch64 { + var ctx: Aarch64 = undefined; + asm volatile ( + \\stp x0, x1, [x0, #0x000] + \\stp x2, x3, [x0, #0x010] + \\stp x4, x5, [x0, #0x020] + \\stp x6, x7, [x0, #0x030] + \\stp x8, x9, [x0, #0x040] + \\stp x10, x11, [x0, #0x050] + \\stp x12, x13, [x0, #0x060] + \\stp x14, x15, [x0, #0x070] + \\stp x16, x17, [x0, #0x080] + \\stp x18, x19, [x0, #0x090] + \\stp x20, x21, [x0, #0x0a0] + \\stp x22, x23, [x0, #0x0b0] + \\stp x24, x25, [x0, #0x0c0] + \\stp x26, x27, [x0, #0x0d0] + \\stp x28, x29, [x0, #0x0e0] + \\str x30, [x0, #0x0f0] + \\mov x1, sp + \\str x1, [x0, #0x0f8] + \\adr x1, . + \\str x1, [x0, #0x100] + \\ldr x1, [x0, #0x008] + : + : [gprs] "{x0}" (&ctx), + : .{ .memory = true }); + return ctx; + } + + pub fn dwarfRegisterBytes(ctx: *Aarch64, register_num: u16) DwarfRegisterError![]u8 { + // DWARF for the Arm(r) 64-bit Architecture (AArch64) § 4.1 "DWARF register names" + switch (register_num) { + 0...30 => return @ptrCast(&ctx.x[register_num]), + 31 => return @ptrCast(&ctx.sp), + 32 => return @ptrCast(&ctx.pc), + + 33 => return error.UnsupportedRegister, // ELR_mode + 34 => return error.UnsupportedRegister, // RA_SIGN_STATE + 35 => return error.UnsupportedRegister, // TPIDRRO_ELO + 36 => return error.UnsupportedRegister, // TPIDR_ELO + 37 => return error.UnsupportedRegister, // TPIDR_EL1 + 38 => return error.UnsupportedRegister, // TPIDR_EL2 + 39 => return error.UnsupportedRegister, // TPIDR_EL3 + 40...45 => return error.UnsupportedRegister, // Reserved + 46 => return error.UnsupportedRegister, // VG + 47 => return error.UnsupportedRegister, // FFR + 48...63 => return error.UnsupportedRegister, // P0 - P15 + 64...95 => return error.UnsupportedRegister, // V0 - V31 + 96...127 => return error.UnsupportedRegister, // Z0 - Z31 + + else => return error.InvalidRegister, + } + } +}; + +/// This is an `extern struct` so that inline assembly in `current` can use field offsets. +const Hexagon = extern struct { + /// The numbered general-purpose registers r0 - r31. + r: [32]u32, + pc: u32, + + pub inline fn current() Hexagon { + var ctx: Hexagon = undefined; + asm volatile ( + \\ memw(r0 + #0) = r0 + \\ memw(r0 + #4) = r1 + \\ memw(r0 + #8) = r2 + \\ memw(r0 + #12) = r3 + \\ memw(r0 + #16) = r4 + \\ memw(r0 + #20) = r5 + \\ memw(r0 + #24) = r6 + \\ memw(r0 + #28) = r7 + \\ memw(r0 + #32) = r8 + \\ memw(r0 + #36) = r9 + \\ memw(r0 + #40) = r10 + \\ memw(r0 + #44) = r11 + \\ memw(r0 + #48) = r12 + \\ memw(r0 + #52) = r13 + \\ memw(r0 + #56) = r14 + \\ memw(r0 + #60) = r15 + \\ memw(r0 + #64) = r16 + \\ memw(r0 + #68) = r17 + \\ memw(r0 + #72) = r18 + \\ memw(r0 + #76) = r19 + \\ memw(r0 + #80) = r20 + \\ memw(r0 + #84) = r21 + \\ memw(r0 + #88) = r22 + \\ memw(r0 + #92) = r23 + \\ memw(r0 + #96) = r24 + \\ memw(r0 + #100) = r25 + \\ memw(r0 + #104) = r26 + \\ memw(r0 + #108) = r27 + \\ memw(r0 + #112) = r28 + \\ memw(r0 + #116) = r29 + \\ memw(r0 + #120) = r30 + \\ memw(r0 + #124) = r31 + \\ r1 = pc + \\ memw(r0 + #128) = r1 + \\ r1 = memw(r0 + #4) + : + : [gprs] "{r0}" (&ctx), + : .{ .memory = true }); + return ctx; + } + + pub fn dwarfRegisterBytes(ctx: *Hexagon, register_num: u16) DwarfRegisterError![]u8 { + // Sourced from LLVM's HexagonRegisterInfo.td, which disagrees with LLDB... + switch (register_num) { + 0...31 => return @ptrCast(&ctx.r[register_num]), + 76 => return @ptrCast(&ctx.pc), + + // This is probably covering some numbers that aren't actually mapped, but seriously, + // look at that file. I really can't be bothered to make it more precise. + 32...75 => return error.UnsupportedRegister, + 77...259 => return error.UnsupportedRegister, + // 999999...1000030 => return error.UnsupportedRegister, + // 9999999...10000030 => return error.UnsupportedRegister, + + else => return error.InvalidRegister, + } + } +}; + +/// This is an `extern struct` so that inline assembly in `current` can use field offsets. +const LoongArch = extern struct { + /// The numbered general-purpose registers r0 - r31. r0 must be zero. + r: [32]Gpr, + pc: Gpr, + + pub const Gpr = if (native_arch == .loongarch64) u64 else u32; + + pub inline fn current() LoongArch { + var ctx: LoongArch = undefined; + asm volatile (if (Gpr == u64) + \\ st.d $zero, $t0, 0 + \\ st.d $ra, $t0, 8 + \\ st.d $tp, $t0, 16 + \\ st.d $sp, $t0, 24 + \\ st.d $a0, $t0, 32 + \\ st.d $a1, $t0, 40 + \\ st.d $a2, $t0, 48 + \\ st.d $a3, $t0, 56 + \\ st.d $a4, $t0, 64 + \\ st.d $a5, $t0, 72 + \\ st.d $a6, $t0, 80 + \\ st.d $a7, $t0, 88 + \\ st.d $t0, $t0, 96 + \\ st.d $t1, $t0, 104 + \\ st.d $t2, $t0, 112 + \\ st.d $t3, $t0, 120 + \\ st.d $t4, $t0, 128 + \\ st.d $t5, $t0, 136 + \\ st.d $t6, $t0, 144 + \\ st.d $t7, $t0, 152 + \\ st.d $t8, $t0, 160 + \\ st.d $r21, $t0, 168 + \\ st.d $fp, $t0, 176 + \\ st.d $s0, $t0, 184 + \\ st.d $s1, $t0, 192 + \\ st.d $s2, $t0, 200 + \\ st.d $s3, $t0, 208 + \\ st.d $s4, $t0, 216 + \\ st.d $s5, $t0, 224 + \\ st.d $s6, $t0, 232 + \\ st.d $s7, $t0, 240 + \\ st.d $s8, $t0, 248 + \\ bl 1f + \\1: + \\ st.d $ra, $t0, 256 + \\ ld.d $ra, $t0, 8 + else + \\ st.w $zero, $t0, 0 + \\ st.w $ra, $t0, 4 + \\ st.w $tp, $t0, 8 + \\ st.w $sp, $t0, 12 + \\ st.w $a0, $t0, 16 + \\ st.w $a1, $t0, 20 + \\ st.w $a2, $t0, 24 + \\ st.w $a3, $t0, 28 + \\ st.w $a4, $t0, 32 + \\ st.w $a5, $t0, 36 + \\ st.w $a6, $t0, 40 + \\ st.w $a7, $t0, 44 + \\ st.w $t0, $t0, 48 + \\ st.w $t1, $t0, 52 + \\ st.w $t2, $t0, 56 + \\ st.w $t3, $t0, 60 + \\ st.w $t4, $t0, 64 + \\ st.w $t5, $t0, 68 + \\ st.w $t6, $t0, 72 + \\ st.w $t7, $t0, 76 + \\ st.w $t8, $t0, 80 + \\ st.w $r21, $t0, 84 + \\ st.w $fp, $t0, 88 + \\ st.w $s0, $t0, 92 + \\ st.w $s1, $t0, 96 + \\ st.w $s2, $t0, 100 + \\ st.w $s3, $t0, 104 + \\ st.w $s4, $t0, 108 + \\ st.w $s5, $t0, 112 + \\ st.w $s6, $t0, 116 + \\ st.w $s7, $t0, 120 + \\ st.w $s8, $t0, 124 + \\ bl 1f + \\1: + \\ st.w $ra, $t0, 128 + \\ ld.w $ra, $t0, 4 + : + : [gprs] "{$r12}" (&ctx), + : .{ .memory = true }); + return ctx; + } + + pub fn dwarfRegisterBytes(ctx: *LoongArch, register_num: u16) DwarfRegisterError![]u8 { + switch (register_num) { + 0...31 => return @ptrCast(&ctx.r[register_num]), + 64 => return @ptrCast(&ctx.pc), + + 32...63 => return error.UnsupportedRegister, // f0 - f31 + + else => return error.InvalidRegister, + } + } +}; + +/// This is an `extern struct` so that inline assembly in `current` can use field offsets. +const Mips = extern struct { + /// The numbered general-purpose registers r0 - r31. r0 must be zero. + r: [32]Gpr, + pc: Gpr, + + pub const Gpr = if (native_arch.isMIPS64()) u64 else u32; + + pub inline fn current() Mips { + var ctx: Mips = undefined; + asm volatile (if (Gpr == u64) + \\ .set push + \\ .set noat + \\ .set noreorder + \\ .set nomacro + \\ sd $zero, 0($t0) + \\ sd $at, 8($t0) + \\ sd $v0, 16($t0) + \\ sd $v1, 24($t0) + \\ sd $a0, 32($t0) + \\ sd $a1, 40($t0) + \\ sd $a2, 48($t0) + \\ sd $a3, 56($t0) + \\ sd $a4, 64($t0) + \\ sd $a5, 72($t0) + \\ sd $a6, 80($t0) + \\ sd $a7, 88($t0) + \\ sd $t0, 96($t0) + \\ sd $t1, 104($t0) + \\ sd $t2, 112($t0) + \\ sd $t3, 120($t0) + \\ sd $s0, 128($t0) + \\ sd $s1, 136($t0) + \\ sd $s2, 144($t0) + \\ sd $s3, 152($t0) + \\ sd $s4, 160($t0) + \\ sd $s5, 168($t0) + \\ sd $s6, 176($t0) + \\ sd $s7, 184($t0) + \\ sd $t8, 192($t0) + \\ sd $t9, 200($t0) + \\ sd $k0, 208($t0) + \\ sd $k1, 216($t0) + \\ sd $gp, 224($t0) + \\ sd $sp, 232($t0) + \\ sd $fp, 240($t0) + \\ sd $ra, 248($t0) + \\ bal 1f + \\1: + \\ sd $ra, 256($t0) + \\ ld $ra, 248($t0) + \\ .set pop + else + \\ .set push + \\ .set noat + \\ .set noreorder + \\ .set nomacro + \\ sw $zero, 0($t4) + \\ sw $at, 4($t4) + \\ sw $v0, 8($t4) + \\ sw $v1, 12($t4) + \\ sw $a0, 16($t4) + \\ sw $a1, 20($t4) + \\ sw $a2, 24($t4) + \\ sw $a3, 28($t4) + \\ sw $t0, 32($t4) + \\ sw $t1, 36($t4) + \\ sw $t2, 40($t4) + \\ sw $t3, 44($t4) + \\ sw $t4, 48($t4) + \\ sw $t5, 52($t4) + \\ sw $t6, 56($t4) + \\ sw $t7, 60($t4) + \\ sw $s0, 64($t4) + \\ sw $s1, 68($t4) + \\ sw $s2, 72($t4) + \\ sw $s3, 76($t4) + \\ sw $s4, 80($t4) + \\ sw $s5, 84($t4) + \\ sw $s6, 88($t4) + \\ sw $s7, 92($t4) + \\ sw $t8, 96($t4) + \\ sw $t9, 100($t4) + \\ sw $k0, 104($t4) + \\ sw $k1, 108($t4) + \\ sw $gp, 112($t4) + \\ sw $sp, 116($t4) + \\ sw $fp, 120($t4) + \\ sw $ra, 124($t4) + \\ bal 1f + \\1: + \\ sw $ra, 128($t4) + \\ lw $ra, 124($t4) + \\ .set pop + : + : [gprs] "{$12}" (&ctx), + : .{ .memory = true }); + return ctx; + } + + pub fn dwarfRegisterBytes(ctx: *Mips, register_num: u16) DwarfRegisterError![]u8 { + switch (register_num) { + 0...31 => return @ptrCast(&ctx.r[register_num]), + 66 => return @ptrCast(&ctx.pc), + + // Who the hell knows what numbers exist for this architecture? What's an ABI + // specification anyway? We don't need that nonsense. + 32...63 => return error.UnsupportedRegister, // f0 - f31, w0 - w31 + 64 => return error.UnsupportedRegister, // hi0 (ac0) + 65 => return error.UnsupportedRegister, // lo0 (ac0) + 176 => return error.UnsupportedRegister, // hi1 (ac1) + 177 => return error.UnsupportedRegister, // lo1 (ac1) + 178 => return error.UnsupportedRegister, // hi2 (ac2) + 179 => return error.UnsupportedRegister, // lo2 (ac2) + 180 => return error.UnsupportedRegister, // hi3 (ac3) + 181 => return error.UnsupportedRegister, // lo3 (ac3) + + else => return error.InvalidRegister, + } + } +}; + +/// This is an `extern struct` so that inline assembly in `current` can use field offsets. +const Powerpc = extern struct { + /// The numbered general-purpose registers r0 - r31. + r: [32]Gpr, + pc: Gpr, + lr: Gpr, + + pub const Gpr = if (native_arch.isPowerPC64()) u64 else u32; + + pub inline fn current() Powerpc { + var ctx: Powerpc = undefined; + asm volatile (if (Gpr == u64) + \\ std 0, 0(10) + \\ std 1, 8(10) + \\ std 2, 16(10) + \\ std 3, 24(10) + \\ std 4, 32(10) + \\ std 5, 40(10) + \\ std 6, 48(10) + \\ std 7, 56(10) + \\ std 8, 64(10) + \\ std 9, 72(10) + \\ std 10, 80(10) + \\ std 11, 88(10) + \\ std 12, 96(10) + \\ std 13, 104(10) + \\ std 14, 112(10) + \\ std 15, 120(10) + \\ std 16, 128(10) + \\ std 17, 136(10) + \\ std 18, 144(10) + \\ std 19, 152(10) + \\ std 20, 160(10) + \\ std 21, 168(10) + \\ std 22, 176(10) + \\ std 23, 184(10) + \\ std 24, 192(10) + \\ std 25, 200(10) + \\ std 26, 208(10) + \\ std 27, 216(10) + \\ std 28, 224(10) + \\ std 29, 232(10) + \\ std 30, 240(10) + \\ std 31, 248(10) + \\ mflr 8 + \\ std 8, 264(10) + \\ bl 1f + \\1: + \\ mflr 8 + \\ std 8, 256(10) + \\ ld 8, 64(10) + else + \\ stw 0, 0(10) + \\ stw 1, 4(10) + \\ stw 2, 8(10) + \\ stw 3, 12(10) + \\ stw 4, 16(10) + \\ stw 5, 20(10) + \\ stw 6, 24(10) + \\ stw 7, 28(10) + \\ stw 8, 32(10) + \\ stw 9, 36(10) + \\ stw 10, 40(10) + \\ stw 11, 44(10) + \\ stw 12, 48(10) + \\ stw 13, 52(10) + \\ stw 14, 56(10) + \\ stw 15, 60(10) + \\ stw 16, 64(10) + \\ stw 17, 68(10) + \\ stw 18, 72(10) + \\ stw 19, 76(10) + \\ stw 20, 80(10) + \\ stw 21, 84(10) + \\ stw 22, 88(10) + \\ stw 23, 92(10) + \\ stw 24, 96(10) + \\ stw 25, 100(10) + \\ stw 26, 104(10) + \\ stw 27, 108(10) + \\ stw 28, 112(10) + \\ stw 29, 116(10) + \\ stw 30, 120(10) + \\ stw 31, 124(10) + \\ mflr 8 + \\ stw 8, 132(10) + \\ bl 1f + \\1: + \\ mflr 8 + \\ stw 8, 128(10) + \\ lwz 8, 32(10) + : + : [gprs] "{r10}" (&ctx), + : .{ .lr = true, .memory = true }); + return ctx; + } + + pub fn dwarfRegisterBytes(ctx: *Powerpc, register_num: u16) DwarfRegisterError![]u8 { + // References: + // + // * System V Application Binary Interface - PowerPC Processor Supplement §3-46 + // * Power Architecture 32-bit Application Binary Interface Supplement 1.0 - Linux & Embedded §3.4 + // * 64-bit ELF V2 ABI Specification - Power Architecture Revision 1.5 §2.4 + // * ??? AIX? + // + // Are we having fun yet? + + if (Gpr == u64) switch (register_num) { + 65 => return @ptrCast(&ctx.lr), // lr + + 66 => return error.UnsupportedRegister, // ctr + 68...75 => return error.UnsupportedRegister, // cr0 - cr7 + 76 => return error.UnsupportedRegister, // xer + 77...108 => return error.UnsupportedRegister, // vr0 - vr31 + 109 => return error.UnsupportedRegister, // vrsave (LLVM) + 110 => return error.UnsupportedRegister, // vscr + 114 => return error.UnsupportedRegister, // tfhar + 115 => return error.UnsupportedRegister, // tfiar + 116 => return error.UnsupportedRegister, // texasr + + else => {}, + } else switch (register_num) { + 65 => return @ptrCast(&ctx.lr), // fpscr (SVR4 / EABI), or lr if you ask LLVM + 108 => return @ptrCast(&ctx.lr), + + 64 => return error.UnsupportedRegister, // cr + 66 => return error.UnsupportedRegister, // msr (SVR4 / EABI), or ctr if you ask LLVM + 68...75 => return error.UnsupportedRegister, // cr0 - cr7 if you ask LLVM + 76 => return error.UnsupportedRegister, // xer if you ask LLVM + 99 => return error.UnsupportedRegister, // acc + 100 => return error.UnsupportedRegister, // mq + 101 => return error.UnsupportedRegister, // xer + 102...107 => return error.UnsupportedRegister, // SPRs + 109 => return error.UnsupportedRegister, // ctr + 110...111 => return error.UnsupportedRegister, // SPRs + 112 => return error.UnsupportedRegister, // spefscr + 113...1123 => return error.UnsupportedRegister, // SPRs + 1124...1155 => return error.UnsupportedRegister, // SPE v0 - v31 + 1200...1231 => return error.UnsupportedRegister, // SPE upper r0 - r31 + 3072...4095 => return error.UnsupportedRegister, // DCRs + 4096...5120 => return error.UnsupportedRegister, // PMRs + + else => {}, + } + + switch (register_num) { + 0...31 => return @ptrCast(&ctx.r[register_num]), + 67 => return @ptrCast(&ctx.pc), + + 32...63 => return error.UnsupportedRegister, // f0 - f31 + + else => return error.InvalidRegister, + } + } +}; + +/// This is an `extern struct` so that inline assembly in `current` can use field offsets. +const Riscv = extern struct { + /// The numbered general-purpose registers r0 - r31. r0 must be zero. + x: [32]Gpr, + pc: Gpr, + + pub const Gpr = if (native_arch.isRiscv64()) u64 else u32; + + pub inline fn current() Riscv { + var ctx: Riscv = undefined; + asm volatile (if (Gpr == u64) + \\ sd zero, 0(t0) + \\ sd ra, 8(t0) + \\ sd sp, 16(t0) + \\ sd gp, 24(t0) + \\ sd tp, 32(t0) + \\ sd t0, 40(t0) + \\ sd t1, 48(t0) + \\ sd t2, 56(t0) + \\ sd s0, 64(t0) + \\ sd s1, 72(t0) + \\ sd a0, 80(t0) + \\ sd a1, 88(t0) + \\ sd a2, 96(t0) + \\ sd a3, 104(t0) + \\ sd a4, 112(t0) + \\ sd a5, 120(t0) + \\ sd a6, 128(t0) + \\ sd a7, 136(t0) + \\ sd s2, 144(t0) + \\ sd s3, 152(t0) + \\ sd s4, 160(t0) + \\ sd s5, 168(t0) + \\ sd s6, 176(t0) + \\ sd s7, 184(t0) + \\ sd s8, 192(t0) + \\ sd s9, 200(t0) + \\ sd s10, 208(t0) + \\ sd s11, 216(t0) + \\ sd t3, 224(t0) + \\ sd t4, 232(t0) + \\ sd t5, 240(t0) + \\ sd t6, 248(t0) + \\ jal ra, 1f + \\1: + \\ sd ra, 256(t0) + \\ ld ra, 8(t0) + else + \\ sw zero, 0(t0) + \\ sw ra, 4(t0) + \\ sw sp, 8(t0) + \\ sw gp, 12(t0) + \\ sw tp, 16(t0) + \\ sw t0, 20(t0) + \\ sw t1, 24(t0) + \\ sw t2, 28(t0) + \\ sw s0, 32(t0) + \\ sw s1, 36(t0) + \\ sw a0, 40(t0) + \\ sw a1, 44(t0) + \\ sw a2, 48(t0) + \\ sw a3, 52(t0) + \\ sw a4, 56(t0) + \\ sw a5, 60(t0) + \\ sw a6, 64(t0) + \\ sw a7, 68(t0) + \\ sw s2, 72(t0) + \\ sw s3, 76(t0) + \\ sw s4, 80(t0) + \\ sw s5, 84(t0) + \\ sw s6, 88(t0) + \\ sw s7, 92(t0) + \\ sw s8, 96(t0) + \\ sw s9, 100(t0) + \\ sw s10, 104(t0) + \\ sw s11, 108(t0) + \\ sw t3, 112(t0) + \\ sw t4, 116(t0) + \\ sw t5, 120(t0) + \\ sw t6, 124(t0) + \\ jal ra, 1f + \\1: + \\ sw ra, 128(t0) + \\ lw ra, 4(t0) + : + : [gprs] "{t0}" (&ctx), + : .{ .memory = true }); + return ctx; + } + + pub fn dwarfRegisterBytes(ctx: *Riscv, register_num: u16) DwarfRegisterError![]u8 { + switch (register_num) { + 0...31 => return @ptrCast(&ctx.x[register_num]), + 65 => return @ptrCast(&ctx.pc), + + 32...63 => return error.UnsupportedRegister, // f0 - f31 + 64 => return error.UnsupportedRegister, // Alternate Frame Return Column + 96...127 => return error.UnsupportedRegister, // v0 - v31 + 3072...4095 => return error.UnsupportedRegister, // Custom extensions + 4096...8191 => return error.UnsupportedRegister, // CSRs + + else => return error.InvalidRegister, + } + } +}; + +/// This is an `extern struct` so that inline assembly in `current` can use field offsets. +const S390x = extern struct { + /// The numbered general-purpose registers r0 - r15. + r: [16]u64, + /// The program counter. + psw: extern struct { + mask: u64, + addr: u64, + }, + + pub inline fn current() S390x { + var ctx: S390x = undefined; + asm volatile ( + \\ stmg %%r0, %%r15, 0(%%r2) + \\ epsw %%r0, %%r1 + \\ stm %%r0, %%r1, 128(%%r2) + \\ larl %%r0, . + \\ stg %%r0, 136(%%r2) + \\ lg %%r0, 0(%%r2) + \\ lg %%r1, 8(%%r2) + : + : [gprs] "{r2}" (&ctx), + : .{ .memory = true }); + return ctx; + } + + pub fn dwarfRegisterBytes(ctx: *S390x, register_num: u16) DwarfRegisterError![]u8 { + switch (register_num) { + 0...15 => return @ptrCast(&ctx.r[register_num]), + 64 => return @ptrCast(&ctx.psw.mask), + 65 => return @ptrCast(&ctx.psw.addr), + + 16...31 => return error.UnsupportedRegister, // f0 - f15 + 32...47 => return error.UnsupportedRegister, // cr0 - cr15 + 48...63 => return error.UnsupportedRegister, // a0 - a15 + 66...67 => return error.UnsupportedRegister, // z/OS stuff??? + 68...83 => return error.UnsupportedRegister, // v16 - v31 + + else => return error.InvalidRegister, + } + } +}; + +/// The native operating system's `ucontext_t` as seen in the third argument to signal handlers. +/// +/// These are dramatically simplified since we only need general-purpose registers and don't care +/// about all the complicated extension state (floating point, vector, etc). This means that these +/// structures are almost all shorter than the real ones, which is safe because we only access them +/// through a pointer. +/// +/// Some effort is made to have structures for the same architecture use the same access pattern, +/// e.g. `uc.mcontext.x` for `aarch64-linux` and `aarch64-freebsd` even though that's not quite how +/// they're declared and spelled in the C headers for both targets. Similarly, registers are typed +/// as unsigned everywhere even if that's not how they're declared in the C headers. +const signal_ucontext_t = switch (native_os) { + .linux => switch (native_arch) { + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/arm64/include/uapi/asm/ucontext.h + .aarch64, + .aarch64_be, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/loongarch/include/uapi/asm/ucontext.h + .loongarch64, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/powerpc/include/uapi/asm/ucontext.h + .powerpc64, + .powerpc64le, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/riscv/include/uapi/asm/ucontext.h + .riscv32, + .riscv64, + => extern struct { + _flags: usize, + _link: ?*signal_ucontext_t, + _stack: std.os.linux.stack_t, + _sigmask: std.os.linux.sigset_t, + _unused: [120]u8, + mcontext: switch (native_arch) { + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/arm64/include/uapi/asm/sigcontext.h + .aarch64, .aarch64_be => extern struct { + _fault_address: u64 align(16), + x: [30]u64, + lr: u64, + sp: u64, + pc: u64, + }, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/loongarch/include/uapi/asm/sigcontext.h + .loongarch64 => extern struct { + pc: u64 align(16), + r: [32]u64, + }, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/powerpc/include/uapi/asm/sigcontext.h + .powerpc64, .powerpc64le => extern struct { + _unused: [4]u64, + _signal: i32, + _pad: i32, + _handler: u64, + _oldmask: u64, + _regs: ?*anyopaque, + r: [32]u64, + pc: u64, + _msr: u64, + _orig_r3: u64, + _ctr: u64, + lr: u64, + }, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/riscv/include/uapi/asm/sigcontext.h + .riscv32, .riscv64 => extern struct { + pc: usize align(16), + ra_sp_gp_tp: [4]usize, + t0_2: [3]usize, + s0_1: [2]usize, + a: [8]usize, + s2_11: [10]usize, + t3_6: [4]usize, + }, + else => unreachable, + }, + }, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/include/uapi/asm-generic/ucontext.h + .arc, + .csky, + .hexagon, + .m68k, + .mips, + .mipsel, + .mips64, + .mips64el, + .or1k, + .s390x, + .x86, + .x86_64, + .xtensa, + => extern struct { + _flags: usize, + _link: ?*signal_ucontext_t, + _stack: std.os.linux.stack_t, + mcontext: switch (native_arch) { + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/arc/include/uapi/asm/sigcontext.h + .arc => extern struct { + _pad1: u32, + _bta: u32, + _lp: extern struct { + _start: u32, + _end: u32, + _count: u32, + }, + _status32: u32, + pc: u32, + r31: u32, + r27_26: [2]u32, + r12_0: [13]u32, + r28: u32, + _pad2: u32, + r25_13: [13]u32, + _efa: u32, + _stop_pc: u32, + r30: u32, + }, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/csky/include/uapi/asm/sigcontext.h + .csky => extern struct { + r31: u32, + r15: u32, + pc: u32, + _sr: u32, + r14: u32, + _orig_a0: u32, + r0_13: [14]u32, + r16_30: [15]u32, + }, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/hexagon/include/uapi/asm/sigcontext.h + .hexagon => extern struct { + r: [32]u32 align(8), + _salc: [2]extern struct { + _sa: u32, + _lc: u32, + }, + _m: [2]u32, + _usr: u32, + _p: u32, + _gp: u32, + _ugp: u32, + pc: u32, + }, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/m68k/include/asm/ucontext.h + .m68k => extern struct { + _version: i32, + d: [8]u32, + a: [8]u32, + pc: u32, + }, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/mips/include/uapi/asm/sigcontext.h + .mips, .mipsel => extern struct { + _regmask: u32, + _status: u32, + // ??? A spectacularly failed attempt to be future-proof? + pc: u64, + r: [32]u64, + }, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/mips/include/uapi/asm/sigcontext.h + .mips64, .mips64el => extern struct { + r: [32]u64, + _fpregs: [32]u64, + _hi: [4]u64, + _lo: [4]u64, + pc: u64, + }, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/openrisc/include/uapi/asm/sigcontext.h + .or1k => extern struct { + r: [32]u32, + pc: u32, + }, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/s390/include/uapi/asm/sigcontext.h + .s390x => extern struct { + psw: extern struct { + mask: u64, + addr: u64, + }, + r: [16]u64, + }, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/x86/include/uapi/asm/sigcontext.h + .x86 => extern struct { + _gs: u32, + _fs: u32, + _es: u32, + _ds: u32, + edi: u32, + esi: u32, + ebp: u32, + esp: u32, + ebx: u32, + edx: u32, + ecx: u32, + eax: u32, + _trapno: u32, + _err: u32, + eip: u32, + }, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/x86/include/uapi/asm/sigcontext.h + .x86_64 => extern struct { + r8: u64, + r9: u64, + r10: u64, + r11: u64, + r12: u64, + r13: u64, + r14: u64, + r15: u64, + rdi: u64, + rsi: u64, + rbp: u64, + rbx: u64, + rdx: u64, + rax: u64, + rcx: u64, + rsp: u64, + rip: u64, + }, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/xtensa/include/uapi/asm/sigcontext.h + .xtensa => extern struct { + pc: u32, + _ps: u32, + _l: extern struct { + _beg: u32, + _end: u32, + _count: u32, + }, + _sar: u32, + _acc: extern struct { + _lo: u32, + _hi: u32, + }, + a: [16]u32, + }, + else => unreachable, + }, + }, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/arm/include/asm/ucontext.h + .arm, .armeb, .thumb, .thumbeb => extern struct { + _flags: u32, + _link: ?*signal_ucontext_t, + _stack: std.os.linux.stack_t, + _unused: [31]i32, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/arm/include/uapi/asm/sigcontext.h + mcontext: extern struct { + _trap_no: u32, + _error_code: u32, + _oldmask: u32, + r: [15]u32, + pc: u32, + }, + }, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/powerpc/include/uapi/asm/ucontext.h + .powerpc, .powerpcle => extern struct { + _flags: u32, + _link: ?*signal_ucontext_t, + _stack: std.os.linux.stack_t, + _pad1: [7]i32, + _regs: ?*anyopaque, + _sigmask: std.os.linux.sigset_t, + _unused: [120]u8, + _pad2: [3]i32, + mcontext: extern struct { + r: [32]u32 align(16), + pc: u32, + _msr: u32, + _orig_r3: u32, + _ctr: u32, + lr: u32, + }, + }, + // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/sparc/include/uapi/asm/uctx.h + .sparc => @compileError("sparc-linux ucontext_t missing"), + .sparc64 => @compileError("sparc64-linux ucontext_t missing"), + else => unreachable, + }, + // https://github.com/freebsd/freebsd-src/blob/55c28005f544282b984ae0e15dacd0c108d8ab12/sys/sys/_ucontext.h + .freebsd => extern struct { + _sigmask: std.c.sigset_t, + mcontext: switch (native_arch) { + // https://github.com/freebsd/freebsd-src/blob/55c28005f544282b984ae0e15dacd0c108d8ab12/sys/arm64/include/ucontext.h + .aarch64 => extern struct { + x: [30]u64 align(16), + lr: u64, + sp: u64, + pc: u64, + }, + // https://github.com/freebsd/freebsd-src/blob/55c28005f544282b984ae0e15dacd0c108d8ab12/sys/arm/include/ucontext.h + .arm => extern struct { + r: [15]u32, + pc: u32, + }, + // https://github.com/freebsd/freebsd-src/blob/55c28005f544282b984ae0e15dacd0c108d8ab12/sys/powerpc/include/ucontext.h + .powerpc64, .powerpc64le => extern struct { + _vers: i32 align(16), + _flags: i32, + _onstack: i32, + _len: i32, + _avec: [32 * 2]u64, + _av: [2]u32, + r: [32]u64, + lr: u64, + _cr: u64, + _xer: u64, + _ctr: u64, + pc: u64, + }, + // https://github.com/freebsd/freebsd-src/blob/55c28005f544282b984ae0e15dacd0c108d8ab12/sys/riscv/include/ucontext.h + .riscv64 => extern struct { + ra_sp_gp_tp: [4]u64, + t0_2: [3]u64, + t3_6: [4]u64, + s0_1: [2]u64, + s2_11: [10]u64, + a: [8]u64, + pc: u64, + }, + // https://github.com/freebsd/freebsd-src/blob/55c28005f544282b984ae0e15dacd0c108d8ab12/sys/x86/include/ucontext.h + .x86_64 => extern struct { + _onstack: i64, + rdi: u64, + rsi: u64, + rdx: u64, + rcx: u64, + r8: u64, + r9: u64, + rax: u64, + rbx: u64, + rbp: u64, + r10: u64, + r11: u64, + r12: u64, + r13: u64, + r14: u64, + r15: u64, + _trapno: i32, + _fs: i16, + _gs: i16, + _addr: i64, + _flags: i32, + _es: i16, + _ds: i16, + _err: i64, + rip: u64, + _cs: i64, + _rflags: i64, + rsp: u64, + }, + else => unreachable, + }, + }, + // https://github.com/ziglang/zig/blob/60be67d3c0ba6ae15fa7115596734ab1e74fbcd3/lib/libc/include/any-macos-any/sys/_types/_ucontext.h + .driverkit, .macos, .ios, .tvos, .watchos, .visionos => extern struct { + _onstack: i32, + _sigmask: std.c.sigset_t, + _stack: std.c.stack_t, + _link: ?*signal_ucontext_t, + _mcsize: u64, + mcontext: *switch (native_arch) { + // https://github.com/ziglang/zig/blob/60be67d3c0ba6ae15fa7115596734ab1e74fbcd3/lib/libc/include/any-macos-any/arm/_mcontext.h + // https://github.com/ziglang/zig/blob/60be67d3c0ba6ae15fa7115596734ab1e74fbcd3/lib/libc/include/any-macos-any/mach/arm/_structs.h + .aarch64 => extern struct { + _far: u64 align(16), + _esr: u64, + x: [30]u64, + lr: u64, + sp: u64, + pc: u64, + }, + // https://github.com/ziglang/zig/blob/60be67d3c0ba6ae15fa7115596734ab1e74fbcd3/lib/libc/include/any-macos-any/i386/_mcontext.h + // https://github.com/ziglang/zig/blob/60be67d3c0ba6ae15fa7115596734ab1e74fbcd3/lib/libc/include/any-macos-any/mach/i386/_structs.h + .x86_64 => extern struct { + _trapno: u16, + _cpu: u16, + _err: u32, + _faultvaddr: u64, + rax: u64, + rbx: u64, + rcx: u64, + rdx: u64, + rdi: u64, + rsi: u64, + rbp: u64, + rsp: u64, + r8: u64, + r9: u64, + r10: u64, + r11: u64, + r12: u64, + r13: u64, + r14: u64, + r15: u64, + rip: u64, + }, + else => unreachable, + }, + }, + // This needs to be audited by someone with access to the Solaris headers. + .solaris => extern struct { + _flags: u64, + _link: ?*signal_ucontext_t, + _sigmask: std.c.sigset_t, + _stack: std.c.stack_t, + mcontext: switch (native_arch) { + .sparc64 => @compileError("sparc64-solaris mcontext_t missing"), + .x86_64 => extern struct { + r15: u64, + r14: u64, + r13: u64, + r12: u64, + r11: u64, + r10: u64, + r9: u64, + r8: u64, + rdi: u64, + rsi: u64, + rbp: u64, + rbx: u64, + rdx: u64, + rcx: u64, + rax: u64, + _trapno: i64, + _err: i64, + rip: u64, + }, + else => unreachable, + }, + }, + // https://github.com/illumos/illumos-gate/blob/d4ce137bba3bd16823db6374d9e9a643264ce245/usr/src/uts/intel/sys/ucontext.h + .illumos => extern struct { + _flags: usize, + _link: ?*signal_ucontext_t, + _sigmask: std.c.sigset_t, + _stack: std.c.stack_t, + mcontext: switch (native_arch) { + // https://github.com/illumos/illumos-gate/blob/d4ce137bba3bd16823db6374d9e9a643264ce245/usr/src/uts/intel/sys/mcontext.h + .x86 => extern struct { + _gs: u32, + _fs: u32, + _es: u32, + _ds: u32, + edi: u32, + esi: u32, + ebp: u32, + esp: u32, + ebx: u32, + edx: u32, + ecx: u32, + eax: u32, + _trapno: i32, + _err: i32, + eip: u32, + }, + .x86_64 => extern struct { + r15: u64, + r14: u64, + r13: u64, + r12: u64, + r11: u64, + r10: u64, + r9: u64, + r8: u64, + rdi: u64, + rsi: u64, + rbp: u64, + rbx: u64, + rdx: u64, + rcx: u64, + rax: u64, + _trapno: i64, + _err: i64, + rip: u64, + }, + else => unreachable, + }, + }, + .openbsd => switch (native_arch) { + // https://github.com/openbsd/src/blob/42468faed8369d07ae49ae02dd71ec34f59b66cd/sys/arch/arm64/include/signal.h + .aarch64 => extern struct { + _unused: i32, + _mask: i32, + mcontext: extern struct { + sp: u64, + lr: u64, + pc: u64, + _spsr: u64, + x: [30]u64, + }, + }, + // https://github.com/openbsd/src/blob/42468faed8369d07ae49ae02dd71ec34f59b66cd/sys/arch/arm/include/signal.h + .arm => extern struct { + _cookie: i32, + _mask: i32, + mcontext: extern struct { + _spsr: u32 align(8), + r: [15]u32, + _svc_lr: u32, + pc: u32, + }, + }, + // https://github.com/openbsd/src/blob/42468faed8369d07ae49ae02dd71ec34f59b66cd/sys/arch/mips64/include/signal.h + .mips64, .mips64el => extern struct { + _cookie: i64, + _mask: i64, + mcontext: extern struct { + pc: u64, + r: [32]u64, + }, + }, + // https://github.com/openbsd/src/blob/42468faed8369d07ae49ae02dd71ec34f59b66cd/sys/arch/powerpc/include/signal.h + // https://github.com/openbsd/src/blob/42468faed8369d07ae49ae02dd71ec34f59b66cd/sys/arch/powerpc64/include/signal.h + .powerpc, .powerpc64 => extern struct { + _cookie: isize, + _mask: i32, + mcontext: extern struct { + r: [32]usize, + lr: usize, + _cr: usize, + _xer: usize, + _ctr: usize, + pc: usize, + }, + }, + // https://github.com/openbsd/src/blob/42468faed8369d07ae49ae02dd71ec34f59b66cd/sys/arch/riscv64/include/signal.h + .riscv64 => extern struct { + _unused: i32, + _mask: i32, + mcontext: extern struct { + ra_sp_gp_tp: [4]u64, + t0_2: [3]u64, + t3_6: [4]u64, + s0_1: [2]u64, + s2_11: [10]u64, + a: [8]u64, + pc: u64, + }, + }, + // https://github.com/openbsd/src/blob/42468faed8369d07ae49ae02dd71ec34f59b66cd/sys/arch/sparc64/include/signal.h + .sparc64 => @compileError("sparc64-openbsd mcontext_t missing"), + // https://github.com/openbsd/src/blob/42468faed8369d07ae49ae02dd71ec34f59b66cd/sys/arch/i386/include/signal.h + .x86 => extern struct { + mcontext: extern struct { + _gs: i32, + _fs: i32, + _es: i32, + _ds: i32, + edi: u32, + esi: u32, + ebp: u32, + ebx: u32, + edx: u32, + ecx: u32, + eax: u32, + eip: u32, + _cs: i32, + _eflags: i32, + esp: u32, + }, + }, + // https://github.com/openbsd/src/blob/42468faed8369d07ae49ae02dd71ec34f59b66cd/sys/arch/amd64/include/signal.h + .x86_64 => extern struct { + mcontext: extern struct { + rdi: u64, + rsi: u64, + rdx: u64, + rcx: u64, + r8: u64, + r9: u64, + r10: u64, + r11: u64, + r12: u64, + r13: u64, + r14: u64, + r15: u64, + rbp: u64, + rbx: u64, + rax: u64, + _gs: i64, + _fs: i64, + _es: i64, + _ds: i64, + _trapno: i64, + _err: i64, + rip: u64, + _cs: i64, + _rflags: i64, + rsp: u64, + }, + }, + else => unreachable, + }, + // https://github.com/NetBSD/src/blob/861008c62187bf7bc0aac4d81e52ed6eee4d0c74/sys/sys/ucontext.h + .netbsd => extern struct { + _flags: u32, + _link: ?*signal_ucontext_t, + _sigmask: std.c.sigset_t, + _stack: std.c.stack_t, + mcontext: switch (native_arch) { + // https://github.com/NetBSD/src/blob/861008c62187bf7bc0aac4d81e52ed6eee4d0c74/sys/arch/arm/include/mcontext.h + .aarch64, .aarch64_be => extern struct { + x: [30]u64 align(16), + lr: u64, + sp: u64, + pc: u64, + }, + .arm, .armeb => extern struct { + r: [15]u32 align(8), + pc: u32, + }, + // https://github.com/NetBSD/src/blob/861008c62187bf7bc0aac4d81e52ed6eee4d0c74/sys/arch/m68k/include/mcontext.h + .m68k => extern struct { + d: [8]u32, + a: [8]u32, + pc: u32, + }, + // https://github.com/NetBSD/src/blob/861008c62187bf7bc0aac4d81e52ed6eee4d0c74/sys/arch/mips/include/mcontext.h + .mips, .mipsel => extern struct { + r: [32]u32 align(8), + _lo: i32, + _hi: i32, + _cause: i32, + pc: u32, + }, + .mips64, .mips64el => @compileError("https://github.com/ziglang/zig/issues/23765#issuecomment-2880386178"), + // https://github.com/NetBSD/src/blob/861008c62187bf7bc0aac4d81e52ed6eee4d0c74/sys/arch/powerpc/include/mcontext.h + .powerpc => extern struct { + r: [32]u32 align(16), + _cr: i32, + lr: u32, + pc: u32, + }, + // https://github.com/NetBSD/src/blob/861008c62187bf7bc0aac4d81e52ed6eee4d0c74/sys/arch/sparc/include/mcontext.h + .sparc => @compileError("sparc-netbsd mcontext_t missing"), + .sparc64 => @compileError("sparc64-netbsd mcontext_t missing"), + // https://github.com/NetBSD/src/blob/861008c62187bf7bc0aac4d81e52ed6eee4d0c74/sys/arch/i386/include/mcontext.h + .x86 => extern struct { + _gs: i32, + _fs: i32, + _es: i32, + _ds: i32, + edi: u32, + esi: u32, + ebp: u32, + esp: u32, + ebx: u32, + edx: u32, + ecx: u32, + eax: u32, + _trapno: i32, + _err: i32, + eip: u32, + }, + // https://github.com/NetBSD/src/blob/861008c62187bf7bc0aac4d81e52ed6eee4d0c74/sys/arch/amd64/include/mcontext.h + .x86_64 => extern struct { + rdi: u64, + rsi: u64, + rdx: u64, + rcx: u64, + r8: u64, + r9: u64, + r10: u64, + r11: u64, + r12: u64, + r13: u64, + r14: u64, + r15: u64, + rbp: u64, + rbx: u64, + rax: u64, + _gs: i64, + _fs: i64, + _es: i64, + _ds: i64, + _trapno: i64, + _err: i64, + rip: u64, + _cs: i64, + _rflags: i64, + rsp: u64, + }, + else => unreachable, + }, + }, + // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/3de1e9d36269616d22237f19d60a737a41271ab5/sys/sys/_ucontext.h + .dragonfly => extern struct { + _sigmask: std.c.sigset_t, + mcontext: switch (native_arch) { + // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/3de1e9d36269616d22237f19d60a737a41271ab5/sys/cpu/x86_64/include/ucontext.h + .x86_64 => extern struct { + _onstack: i64 align(64), + rdi: u64, + rsi: u64, + rdx: u64, + rcx: u64, + r8: u64, + r9: u64, + rax: u64, + rbx: u64, + rbp: u64, + r10: u64, + r11: u64, + r12: u64, + r13: u64, + r14: u64, + r15: u64, + _xflags: i64, + _trapno: i64, + _addr: i64, + _flags: i64, + _err: i64, + rip: u64, + _cs: i64, + _rflags: i64, + rsp: u64, + }, + else => unreachable, + }, + }, + // https://github.com/SerenityOS/serenity/blob/103d6a07de9e28f3c94dfa2351b9af76a49ba6e6/Kernel/API/POSIX/ucontext.h + .serenity => extern struct { + _link: ?*signal_ucontext_t, + _sigmask: std.c.sigset_t, + _stack: std.c.stack_t, + mcontext: switch (native_arch) { + // https://github.com/SerenityOS/serenity/blob/103d6a07de9e28f3c94dfa2351b9af76a49ba6e6/Kernel/Arch/aarch64/mcontext.h + .aarch64 => extern struct { + x: [30]u64, + lr: u64, + sp: u64, + pc: u64, + }, + // https://github.com/SerenityOS/serenity/blob/103d6a07de9e28f3c94dfa2351b9af76a49ba6e6/Kernel/Arch/riscv64/mcontext.h + .riscv64 => extern struct { + ra_sp_gp_tp: [4]u64, + t0_2: [3]u64, + s0_1: [2]u64, + a: [8]u64, + s2_11: [10]u64, + t3_6: [4]u64, + pc: u64, + }, + // https://github.com/SerenityOS/serenity/blob/103d6a07de9e28f3c94dfa2351b9af76a49ba6e6/Kernel/Arch/x86_64/mcontext.h + .x86_64 => extern struct { + rax: u64, + rcx: u64, + rdx: u64, + rbx: u64, + rsp: u64, + rbp: u64, + rsi: u64, + rdi: u64, + rip: u64, + r8: u64, + r9: u64, + r10: u64, + r11: u64, + r12: u64, + r13: u64, + r14: u64, + r15: u64, + }, + else => unreachable, + }, + }, + // https://github.com/haiku/haiku/blob/47538c534fe0aadc626c09d121773fee8ea10d71/headers/posix/signal.h#L356 + .haiku => extern struct { + _link: ?*signal_ucontext_t, + _sigmask: std.c.sigset_t, + _stack: std.c.stack_t, + mcontext: switch (native_arch) { + // https://github.com/haiku/haiku/blob/47538c534fe0aadc626c09d121773fee8ea10d71/headers/posix/arch/arm/signal.h + .arm => extern struct { + r: [15]u32 align(8), + pc: u32, + }, + // https://github.com/haiku/haiku/blob/47538c534fe0aadc626c09d121773fee8ea10d71/headers/posix/arch/arm64/signal.h + .aarch64 => extern struct { + x: [30]u64 align(16), + lr: u64, + sp: u64, + pc: u64, + }, + // https://github.com/haiku/haiku/blob/47538c534fe0aadc626c09d121773fee8ea10d71/headers/posix/arch/m68k/signal.h + .m68k => extern struct { + pc: u32 align(8), + d: [8]u32, + a: [8]u32, + }, + // https://github.com/haiku/haiku/blob/47538c534fe0aadc626c09d121773fee8ea10d71/headers/posix/arch/ppc/signal.h + .powerpc => extern struct { + pc: u32 align(8), + r: [13]u32, // Um, are you okay, Haiku? + _f: [14]f64, + _reserved: u32, + _fpscr: u32, + _ctr: u32, + _xer: u32, + _cr: u32, + _msr: u32, + lr: u32, + }, + // https://github.com/haiku/haiku/blob/47538c534fe0aadc626c09d121773fee8ea10d71/headers/posix/arch/riscv64/signal.h + .riscv64 => extern struct { + ra_sp_gp_tp: [4]u64, + t0_2: [3]u64, + s0_1: [2]u64, + a: [8]u64, + s2_11: [10]u64, + t3_6: [4]u64, + pc: u64, + }, + // https://github.com/haiku/haiku/blob/47538c534fe0aadc626c09d121773fee8ea10d71/headers/posix/arch/sparc64/signal.h + .sparc64 => @compileError("sparc64-haiku mcontext_t missing"), + // https://github.com/haiku/haiku/blob/47538c534fe0aadc626c09d121773fee8ea10d71/headers/posix/arch/x86/signal.h + .x86 => extern struct { + eip: u32, + _eflags: u32, + eax: u32, + ecx: u32, + edx: u32, + esp: u32, + ebp: u32, + _reserved: u32, + _xregs: extern struct { + _fp_control: u16, + _fp_status: u16, + _fp_tag: u16, + _fp_opcode: u16, + _fp_eip: u32, + _fp_cs: u16, + _reserved1: u16, + _fp_datap: u32, + _fp_ds: u16, + _reserved2: u16, + _mxcsr: u32, + _mxcsr_mask: u32, + _mmx: [8][16]u8, + _xmmx: [8][16]u8, + _reserved3: [176]u8, + _fault_address: u32, + _error_code: u32, + _cs: u16, + _ds: u16, + _es: u16, + _fs: u16, + _gs: u16, + _ss: u16, + _trap_number: u8, + _reserved4: [27]u8, + _format: u32, + }, + edi: u32, + esi: u32, + ebx: u32, + }, + // https://github.com/haiku/haiku/blob/47538c534fe0aadc626c09d121773fee8ea10d71/headers/posix/arch/x86_64/signal.h + .x86_64 => extern struct { + rax: u64, + rbx: u64, + rcx: u64, + rdx: u64, + rdi: u64, + rsi: u64, + rbp: u64, + r8: u64, + r9: u64, + r10: u64, + r11: u64, + r12: u64, + r13: u64, + r14: u64, + r15: u64, + rsp: u64, + rip: u64, + }, + else => unreachable, + }, + }, + else => void, +}; + +const std = @import("../std.zig"); +const root = @import("root"); +const builtin = @import("builtin"); +const native_arch = @import("builtin").target.cpu.arch; +const native_os = @import("builtin").target.os.tag; diff --git a/lib/std/dwarf/EH.zig b/lib/std/dwarf/EH.zig index 3ee7e0be0f..97f1ffbfe2 100644 --- a/lib/std/dwarf/EH.zig +++ b/lib/std/dwarf/EH.zig @@ -1,27 +1,34 @@ -pub const PE = struct { - pub const absptr = 0x00; +pub const PE = packed struct(u8) { + type: Type, + rel: Rel, + /// Undocumented GCC extension + indirect: bool = false, - pub const size_mask = 0x7; - pub const sign_mask = 0x8; - pub const type_mask = size_mask | sign_mask; + /// This is a special encoding which does not correspond to named `type`/`rel` values. + pub const omit: PE = @bitCast(@as(u8, 0xFF)); - pub const uleb128 = 0x01; - pub const udata2 = 0x02; - pub const udata4 = 0x03; - pub const udata8 = 0x04; - pub const sleb128 = 0x09; - pub const sdata2 = 0x0A; - pub const sdata4 = 0x0B; - pub const sdata8 = 0x0C; + pub const Type = enum(u4) { + absptr = 0x0, + uleb128 = 0x1, + udata2 = 0x2, + udata4 = 0x3, + udata8 = 0x4, + sleb128 = 0x9, + sdata2 = 0xA, + sdata4 = 0xB, + sdata8 = 0xC, + _, + }; - pub const rel_mask = 0x70; - pub const pcrel = 0x10; - pub const textrel = 0x20; - pub const datarel = 0x30; - pub const funcrel = 0x40; - pub const aligned = 0x50; - - pub const indirect = 0x80; - - pub const omit = 0xff; + /// The specification considers this a `u4`, but the GCC `indirect` field extension conflicts + /// with that, so we consider it a `u3` instead. + pub const Rel = enum(u3) { + abs = 0x0, + pcrel = 0x1, + textrel = 0x2, + datarel = 0x3, + funcrel = 0x4, + aligned = 0x5, + _, + }; }; diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index bac31f5760..9504242cd5 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -95,8 +95,7 @@ pub fn get_DYNAMIC() ?[*]const elf.Dyn { pub fn linkmap_iterator(phdrs: []const elf.Phdr) error{InvalidExe}!LinkMap.Iterator { _ = phdrs; const _DYNAMIC = get_DYNAMIC() orelse { - // No PT_DYNAMIC means this is either a statically-linked program or a - // badly corrupted dynamically-linked one. + // No PT_DYNAMIC means this is a statically-linked non-PIE program. return .{ .current = null }; }; diff --git a/lib/std/elf.zig b/lib/std/elf.zig index 43b542c09e..3b0c085003 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -744,7 +744,8 @@ pub const SectionHeaderBufferIterator = struct { const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr); const offset = it.elf_header.shoff + size * it.index; - var reader = std.Io.Reader.fixed(it.buf[offset..]); + if (offset > it.buf.len) return error.EndOfStream; + var reader = std.Io.Reader.fixed(it.buf[@intCast(offset)..]); return takeShdr(&reader, it.elf_header); } diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 03fd679495..6bfc41cd79 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -31,6 +31,7 @@ pub const wasi = @import("fs/wasi.zig"); pub const realpath = posix.realpath; pub const realpathZ = posix.realpathZ; pub const realpathW = posix.realpathW; +pub const realpathW2 = posix.realpathW2; pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir; pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError; @@ -45,7 +46,7 @@ pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirE /// The byte count includes room for a null sentinel byte. /// /// * On Windows, `[]u8` file paths are encoded as -/// [WTF-8](https://simonsapin.github.io/wtf-8/). +/// [WTF-8](https://wtf-8.codeberg.page/). /// * On WASI, `[]u8` file paths are encoded as valid UTF-8. /// * On other platforms, `[]u8` file paths are opaque sequences of bytes with /// no particular encoding. @@ -67,7 +68,7 @@ pub const max_path_bytes = switch (native_os) { /// operations are likely to fit into a `u8` array of this length, but /// (depending on the platform) this assumption may not hold for every configuration. /// The byte count does not include a null sentinel byte. -/// On Windows, `[]u8` file name components are encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `[]u8` file name components are encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, file name components are encoded as valid UTF-8. /// On other platforms, `[]u8` components are an opaque sequence of bytes with no particular encoding. pub const max_name_bytes = switch (native_os) { @@ -99,7 +100,7 @@ pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, null); /// Same as `Dir.updateFile`, except asserts that both `source_path` and `dest_path` /// are absolute. See `Dir.updateFile` for a function that operates on both /// absolute and relative paths. -/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, both paths should be encoded as valid UTF-8. /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. pub fn updateFileAbsolute( @@ -116,7 +117,7 @@ pub fn updateFileAbsolute( /// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path` /// are absolute. See `Dir.copyFile` for a function that operates on both /// absolute and relative paths. -/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, both paths should be encoded as valid UTF-8. /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. pub fn copyFileAbsolute( @@ -133,7 +134,7 @@ pub fn copyFileAbsolute( /// Create a new directory, based on an absolute path. /// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates /// on both absolute and relative paths. -/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `absolute_path` should be encoded as valid UTF-8. /// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. pub fn makeDirAbsolute(absolute_path: []const u8) !void { @@ -154,7 +155,7 @@ pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void { } /// Same as `Dir.deleteDir` except the path is absolute. -/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `dir_path` should be encoded as valid UTF-8. /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. pub fn deleteDirAbsolute(dir_path: []const u8) !void { @@ -175,7 +176,7 @@ pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void { } /// Same as `Dir.rename` except the paths are absolute. -/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, both paths should be encoded as valid UTF-8. /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void { @@ -237,7 +238,7 @@ pub fn defaultWasiCwd() std.os.wasi.fd_t { /// See `openDirAbsoluteZ` for a function that accepts a null-terminated path. /// /// Asserts that the path parameter has no null bytes. -/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `absolute_path` should be encoded as valid UTF-8. /// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenOptions) File.OpenError!Dir { @@ -262,7 +263,7 @@ pub fn openDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenOptions) /// operates on both absolute and relative paths. /// Asserts that the path parameter has no null bytes. See `openFileAbsoluteZ` for a function /// that accepts a null-terminated path. -/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `absolute_path` should be encoded as valid UTF-8. /// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.OpenError!File { @@ -287,7 +288,7 @@ pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) Fi /// For example, instead of testing if a file exists and then opening it, just /// open it and handle the error for file not found. /// See `accessAbsoluteZ` for a function that accepts a null-terminated path. -/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `absolute_path` should be encoded as valid UTF-8. /// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. pub fn accessAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Dir.AccessError!void { @@ -311,7 +312,7 @@ pub fn accessAbsoluteW(absolute_path: [*:0]const u16, flags: File.OpenFlags) Dir /// operates on both absolute and relative paths. /// Asserts that the path parameter has no null bytes. See `createFileAbsoluteC` for a function /// that accepts a null-terminated path. -/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `absolute_path` should be encoded as valid UTF-8. /// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. pub fn createFileAbsolute(absolute_path: []const u8, flags: File.CreateFlags) File.OpenError!File { @@ -335,7 +336,7 @@ pub fn createFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.CreateFl /// Asserts that the path is absolute. See `Dir.deleteFile` for a function that /// operates on both absolute and relative paths. /// Asserts that the path parameter has no null bytes. -/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `absolute_path` should be encoded as valid UTF-8. /// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void { @@ -360,7 +361,7 @@ pub fn deleteFileAbsoluteW(absolute_path_w: [*:0]const u16) Dir.DeleteFileError! /// Asserts that the path is absolute. See `Dir.deleteTree` for a function that /// operates on both absolute and relative paths. /// Asserts that the path parameter has no null bytes. -/// On Windows, `absolute_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `absolute_path` should be encoded as valid UTF-8. /// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding. pub fn deleteTreeAbsolute(absolute_path: []const u8) !void { @@ -378,7 +379,7 @@ pub fn deleteTreeAbsolute(absolute_path: []const u8) !void { } /// Same as `Dir.readLink`, except it asserts the path is absolute. -/// On Windows, `pathname` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `pathname` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `pathname` should be encoded as valid UTF-8. /// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding. pub fn readLinkAbsolute(pathname: []const u8, buffer: *[max_path_bytes]u8) ![]u8 { @@ -404,7 +405,7 @@ pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[max_path_bytes]u8) /// one; the latter case is known as a dangling link. /// If `sym_link_path` exists, it will not be overwritten. /// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`. -/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, both paths should be encoded as valid UTF-8. /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. pub fn symLinkAbsolute( @@ -536,7 +537,7 @@ pub fn selfExePathAlloc(allocator: Allocator) ![]u8 { /// This function may return an error if the current executable /// was deleted after spawning. /// Returned value is a slice of out_buffer. -/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. /// /// On Linux, depends on procfs being mounted. If the currently executing binary has @@ -642,11 +643,19 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { // If ImagePathName is a symlink, then it will contain the path of the // symlink, not the path that the symlink points to. We want the path // that the symlink points to, though, so we need to get the realpath. - const pathname_w = try windows.wToPrefixedFileW(null, image_path_name); - return std.fs.cwd().realpathW(pathname_w.span(), out_buffer) catch |err| switch (err) { + var pathname_w = try windows.wToPrefixedFileW(null, image_path_name); + + const wide_slice = std.fs.cwd().realpathW2(pathname_w.span(), &pathname_w.data) catch |err| switch (err) { error.InvalidWtf8 => unreachable, else => |e| return e, }; + + const len = std.unicode.calcWtf8Len(wide_slice); + if (len > out_buffer.len) + return error.NameTooLong; + + const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); + return out_buffer[0..end_index]; }, else => @compileError("std.fs.selfExePath not supported for this target"), } @@ -668,7 +677,7 @@ pub fn selfExeDirPathAlloc(allocator: Allocator) ![]u8 { /// Get the directory path that contains the current executable. /// Returned value is a slice of out_buffer. -/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 { const self_exe_path = try selfExePath(out_buffer); @@ -678,7 +687,7 @@ pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 { } /// `realpath`, except caller must free the returned memory. -/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. /// See also `Dir.realpath`. pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 { diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index d097573122..67f0c0d724 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -663,35 +663,17 @@ fn iterateImpl(self: Dir, first_iter_start_value: bool) Iterator { } } -pub const Walker = struct { - stack: std.ArrayListUnmanaged(StackItem), +pub const SelectiveWalker = struct { + stack: std.ArrayListUnmanaged(Walker.StackItem), name_buffer: std.ArrayListUnmanaged(u8), allocator: Allocator, - pub const Entry = struct { - /// The containing directory. This can be used to operate directly on `basename` - /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths. - /// The directory remains open until `next` or `deinit` is called. - dir: Dir, - basename: [:0]const u8, - path: [:0]const u8, - kind: Dir.Entry.Kind, - }; - - const StackItem = struct { - iter: Dir.Iterator, - dirname_len: usize, - }; - /// After each call to this function, and on deinit(), the memory returned /// from this function becomes invalid. A copy must be made in order to keep /// a reference to the path. - pub fn next(self: *Walker) !?Walker.Entry { - const gpa = self.allocator; - while (self.stack.items.len != 0) { - // `top` and `containing` become invalid after appending to `self.stack` - var top = &self.stack.items[self.stack.items.len - 1]; - var containing = top; + pub fn next(self: *SelectiveWalker) !?Walker.Entry { + while (self.stack.items.len > 0) { + const top = &self.stack.items[self.stack.items.len - 1]; var dirname_len = top.dirname_len; if (top.iter.next() catch |err| { // If we get an error, then we want the user to be able to continue @@ -703,36 +685,22 @@ pub const Walker = struct { item.iter.dir.close(); } return err; - }) |base| { + }) |entry| { self.name_buffer.shrinkRetainingCapacity(dirname_len); if (self.name_buffer.items.len != 0) { - try self.name_buffer.append(gpa, fs.path.sep); + try self.name_buffer.append(self.allocator, fs.path.sep); dirname_len += 1; } - try self.name_buffer.ensureUnusedCapacity(gpa, base.name.len + 1); - self.name_buffer.appendSliceAssumeCapacity(base.name); + try self.name_buffer.ensureUnusedCapacity(self.allocator, entry.name.len + 1); + self.name_buffer.appendSliceAssumeCapacity(entry.name); self.name_buffer.appendAssumeCapacity(0); - if (base.kind == .directory) { - var new_dir = top.iter.dir.openDir(base.name, .{ .iterate = true }) catch |err| switch (err) { - error.NameTooLong => unreachable, // no path sep in base.name - else => |e| return e, - }; - { - errdefer new_dir.close(); - try self.stack.append(gpa, .{ - .iter = new_dir.iterateAssumeFirstIteration(), - .dirname_len = self.name_buffer.items.len - 1, - }); - top = &self.stack.items[self.stack.items.len - 1]; - containing = &self.stack.items[self.stack.items.len - 2]; - } - } - return .{ - .dir = containing.iter.dir, + const walker_entry: Walker.Entry = .{ + .dir = top.iter.dir, .basename = self.name_buffer.items[dirname_len .. self.name_buffer.items.len - 1 :0], .path = self.name_buffer.items[0 .. self.name_buffer.items.len - 1 :0], - .kind = base.kind, + .kind = entry.kind, }; + return walker_entry; } else { var item = self.stack.pop().?; if (self.stack.items.len != 0) { @@ -743,20 +711,46 @@ pub const Walker = struct { return null; } - pub fn deinit(self: *Walker) void { - const gpa = self.allocator; - // Close any remaining directories except the initial one (which is always at index 0) - if (self.stack.items.len > 1) { - for (self.stack.items[1..]) |*item| { - item.iter.dir.close(); + /// Traverses into the directory, continuing walking one level down. + pub fn enter(self: *SelectiveWalker, entry: Walker.Entry) !void { + if (entry.kind != .directory) { + @branchHint(.cold); + return; + } + + var new_dir = entry.dir.openDir(entry.basename, .{ .iterate = true }) catch |err| { + switch (err) { + error.NameTooLong => unreachable, + else => |e| return e, } + }; + errdefer new_dir.close(); + + try self.stack.append(self.allocator, .{ + .iter = new_dir.iterateAssumeFirstIteration(), + .dirname_len = self.name_buffer.items.len - 1, + }); + } + + pub fn deinit(self: *SelectiveWalker) void { + self.name_buffer.deinit(self.allocator); + self.stack.deinit(self.allocator); + } + + /// Leaves the current directory, continuing walking one level up. + /// If the current entry is a directory entry, then the "current directory" + /// will pertain to that entry if `enter` is called before `leave`. + pub fn leave(self: *SelectiveWalker) void { + var item = self.stack.pop().?; + if (self.stack.items.len != 0) { + @branchHint(.likely); + item.iter.dir.close(); } - self.stack.deinit(gpa); - self.name_buffer.deinit(gpa); } }; -/// Recursively iterates over a directory. +/// Recursively iterates over a directory, but requires the user to +/// opt-in to recursing into each directory entry. /// /// `self` must have been opened with `OpenOptions{.iterate = true}`. /// @@ -765,7 +759,9 @@ pub const Walker = struct { /// The order of returned file system entries is undefined. /// /// `self` will not be closed after walking it. -pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker { +/// +/// See also `walk`. +pub fn walkSelectively(self: Dir, allocator: Allocator) !SelectiveWalker { var stack: std.ArrayListUnmanaged(Walker.StackItem) = .empty; try stack.append(allocator, .{ @@ -780,6 +776,71 @@ pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker { }; } +pub const Walker = struct { + inner: SelectiveWalker, + + pub const Entry = struct { + /// The containing directory. This can be used to operate directly on `basename` + /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths. + /// The directory remains open until `next` or `deinit` is called. + dir: Dir, + basename: [:0]const u8, + path: [:0]const u8, + kind: Dir.Entry.Kind, + + /// Returns the depth of the entry relative to the initial directory. + /// Returns 1 for a direct child of the initial directory, 2 for an entry + /// within a direct child of the initial directory, etc. + pub fn depth(self: Walker.Entry) usize { + return mem.countScalar(u8, self.path, fs.path.sep) + 1; + } + }; + + const StackItem = struct { + iter: Dir.Iterator, + dirname_len: usize, + }; + + /// After each call to this function, and on deinit(), the memory returned + /// from this function becomes invalid. A copy must be made in order to keep + /// a reference to the path. + pub fn next(self: *Walker) !?Walker.Entry { + const entry = try self.inner.next(); + if (entry != null and entry.?.kind == .directory) { + try self.inner.enter(entry.?); + } + return entry; + } + + pub fn deinit(self: *Walker) void { + self.inner.deinit(); + } + + /// Leaves the current directory, continuing walking one level up. + /// If the current entry is a directory entry, then the "current directory" + /// is the directory pertaining to the current entry. + pub fn leave(self: *Walker) void { + self.inner.leave(); + } +}; + +/// Recursively iterates over a directory. +/// +/// `self` must have been opened with `OpenOptions{.iterate = true}`. +/// +/// `Walker.deinit` releases allocated memory and directory handles. +/// +/// The order of returned file system entries is undefined. +/// +/// `self` will not be closed after walking it. +/// +/// See also `walkSelectively`. +pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker { + return .{ + .inner = try walkSelectively(self, allocator), + }; +} + pub const OpenError = error{ FileNotFound, NotDir, @@ -794,7 +855,7 @@ pub const OpenError = error{ /// WASI-only; file paths must be valid UTF-8. InvalidUtf8, /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ + /// https://wtf-8.codeberg.page/ InvalidWtf8, BadPathName, DeviceBusy, @@ -812,7 +873,7 @@ pub fn close(self: *Dir) void { /// To create a new file, see `createFile`. /// Call `File.close` to release the resource. /// Asserts that the path parameter has no null bytes. -/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File { @@ -938,7 +999,7 @@ pub fn openFileZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File } /// Same as `openFile` but Windows-only and the path parameter is -/// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded. +/// [WTF-16](https://wtf-8.codeberg.page/#potentially-ill-formed-utf-16) encoded. pub fn openFileW(self: Dir, sub_path_w: []const u16, flags: File.OpenFlags) File.OpenError!File { const w = windows; const file: File = .{ @@ -977,7 +1038,7 @@ pub fn openFileW(self: Dir, sub_path_w: []const u16, flags: File.OpenFlags) File /// Creates, opens, or overwrites a file with write access. /// Call `File.close` on the result when done. /// Asserts that the path parameter has no null bytes. -/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File { @@ -1089,7 +1150,7 @@ pub fn createFileZ(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags } /// Same as `createFile` but Windows-only and the path parameter is -/// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded. +/// [WTF-16](https://wtf-8.codeberg.page/#potentially-ill-formed-utf-16) encoded. pub fn createFileW(self: Dir, sub_path_w: []const u16, flags: File.CreateFlags) File.OpenError!File { const w = windows; const read_flag = if (flags.read) @as(u32, w.GENERIC_READ) else 0; @@ -1134,7 +1195,7 @@ pub const MakeError = posix.MakeDirError; /// Creates a single directory with a relative or absolute path. /// To create multiple directories to make an entire path, see `makePath`. /// To operate on only absolute paths, see `makeDirAbsolute`. -/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. pub fn makeDir(self: Dir, sub_path: []const u8) MakeError!void { @@ -1160,7 +1221,7 @@ pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) MakeError!void { /// Returns success if the path already exists and is a directory. /// This function is not atomic, and if it returns an error, the file system may /// have been modified regardless. -/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. /// Fails on an empty path with `error.BadPathName` as that is not a path that can be created. @@ -1214,7 +1275,7 @@ pub fn makePathStatus(self: Dir, sub_path: []const u8) (MakeError || StatFileErr /// Opens the dir if the path already exists and is a directory. /// This function is not atomic, and if it returns an error, the file system may /// have been modified regardless. -/// `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) (MakeError || OpenError || StatFileError)!Dir { const w = windows; var it = try fs.path.componentIterator(sub_path); @@ -1265,7 +1326,7 @@ fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no /// This function performs `makePath`, followed by `openDir`. /// If supported by the OS, this operation is atomic. It is not atomic on /// all operating systems. -/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenOptions) (MakeError || OpenError || StatFileError)!Dir { @@ -1296,9 +1357,9 @@ pub const RealPathError = posix.RealPathError; /// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this /// `Dir` handle and returns the canonicalized absolute pathname of `pathname` /// argument. -/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. -/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. /// This function is not universally supported by all platforms. /// Currently supported hosts are: Linux, macOS, and Windows. @@ -1308,8 +1369,16 @@ pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) RealPathError @compileError("realpath is not available on WASI"); } if (native_os == .windows) { - const pathname_w = try windows.sliceToPrefixedFileW(self.fd, pathname); - return self.realpathW(pathname_w.span(), out_buffer); + var pathname_w = try windows.sliceToPrefixedFileW(self.fd, pathname); + + const wide_slice = try self.realpathW2(pathname_w.span(), &pathname_w.data); + + const len = std.unicode.calcWtf8Len(wide_slice); + if (len > out_buffer.len) + return error.NameTooLong; + + const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); + return out_buffer[0..end_index]; } const pathname_c = try posix.toPosixPath(pathname); return self.realpathZ(&pathname_c, out_buffer); @@ -1319,8 +1388,16 @@ pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) RealPathError /// See also `Dir.realpath`, `realpathZ`. pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathError![]u8 { if (native_os == .windows) { - const pathname_w = try windows.cStrToPrefixedFileW(self.fd, pathname); - return self.realpathW(pathname_w.span(), out_buffer); + var pathname_w = try windows.cStrToPrefixedFileW(self.fd, pathname); + + const wide_slice = try self.realpathW2(pathname_w.span(), &pathname_w.data); + + const len = std.unicode.calcWtf8Len(wide_slice); + if (len > out_buffer.len) + return error.NameTooLong; + + const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); + return out_buffer[0..end_index]; } var flags: posix.O = .{}; @@ -1349,10 +1426,31 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE return result; } +/// Deprecated: use `realpathW2`. +/// /// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 LE encoded. -/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// See also `Dir.realpath`, `realpathW`. pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathError![]u8 { + var wide_buf: [std.os.windows.PATH_MAX_WIDE]u16 = undefined; + const wide_slice = try self.realpathW2(pathname, &wide_buf); + + const len = std.unicode.calcWtf8Len(wide_slice); + if (len > out_buffer.len) return error.NameTooLong; + + const end_index = std.unicode.wtf16LeToWtf8(&out_buffer, wide_slice); + return out_buffer[0..end_index]; +} + +/// Windows-only. Same as `Dir.realpath` except +/// * `pathname` and the result are WTF-16 LE encoded +/// * `pathname` is relative or has the NT namespace prefix. See `windows.wToPrefixedFileW` for details. +/// +/// Additionally, `pathname` will never be accessed after `out_buffer` has been written to, so it +/// is safe to reuse a single buffer for both. +/// +/// See also `Dir.realpath`, `realpathW`. +pub fn realpathW2(self: Dir, pathname: []const u16, out_buffer: []u16) RealPathError![]u16 { const w = windows; const access_mask = w.GENERIC_READ | w.SYNCHRONIZE; @@ -1373,13 +1471,7 @@ pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathErr }; defer w.CloseHandle(h_file); - var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined; - const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &wide_buf); - const len = std.unicode.calcWtf8Len(wide_slice); - if (len > out_buffer.len) - return error.NameTooLong; - const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); - return out_buffer[0..end_index]; + return w.GetFinalPathNameByHandle(h_file, .{}, out_buffer); } pub const RealPathAllocError = RealPathError || Allocator.Error; @@ -1437,7 +1529,7 @@ pub const OpenOptions = struct { /// open until `close` is called on the result. /// The directory cannot be iterated unless the `iterate` option is set to `true`. /// -/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. /// Asserts that the path parameter has no null bytes. @@ -1652,7 +1744,7 @@ fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u3 pub const DeleteFileError = posix.UnlinkError; /// Delete a file name and possibly the file it refers to, based on an open directory handle. -/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. /// Asserts that the path parameter has no null bytes. @@ -1713,7 +1805,7 @@ pub const DeleteDirError = error{ /// WASI-only; file paths must be valid UTF-8. InvalidUtf8, /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ + /// https://wtf-8.codeberg.page/ InvalidWtf8, BadPathName, /// On Windows, `\\server` or `\\server\share` was not found. @@ -1724,7 +1816,7 @@ pub const DeleteDirError = error{ /// Returns `error.DirNotEmpty` if the directory is not empty. /// To delete a directory recursively, see `deleteTree`. -/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. /// Asserts that the path parameter has no null bytes. @@ -1766,7 +1858,7 @@ pub const RenameError = posix.RenameError; /// If new_sub_path already exists, it will be replaced. /// Renaming a file over an existing directory or a directory /// over an existing file will fail with `error.IsDir` or `error.NotDir` -/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, both paths should be encoded as valid UTF-8. /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void { @@ -1797,7 +1889,7 @@ pub const SymLinkFlags = struct { /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent /// one; the latter case is known as a dangling link. /// If `sym_link_path` exists, it will not be overwritten. -/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, both paths should be encoded as valid UTF-8. /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. pub fn symLink( @@ -1880,7 +1972,7 @@ pub fn symLinkW( /// Same as `symLink`, except tries to create the symbolic link until it /// succeeds or encounters an error other than `error.PathAlreadyExists`. /// -/// * On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// * On WASI, both paths should be encoded as valid UTF-8. /// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding. pub fn atomicSymLink( @@ -1926,7 +2018,7 @@ pub const ReadLinkError = posix.ReadLinkError; /// Read value of a symbolic link. /// The return value is a slice of `buffer`, from index `0`. /// Asserts that the path parameter has no null bytes. -/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u8 { @@ -1966,7 +2058,7 @@ pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 { /// the situation is ambiguous. It could either mean that the entire file was read, and /// it exactly fits the buffer, or it could mean the buffer was not big enough for the /// entire file. -/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `file_path` should be encoded as valid UTF-8. /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 { @@ -1993,7 +2085,7 @@ pub const ReadFileAllocError = File.OpenError || File.ReadError || Allocator.Err /// `File.Reader` which handles this seamlessly. pub fn readFileAlloc( dir: Dir, - /// On Windows, should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). + /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, should be encoded as valid UTF-8. /// On other platforms, an opaque sequence of bytes with no particular encoding. sub_path: []const u8, @@ -2012,7 +2104,7 @@ pub fn readFileAlloc( /// `File.Reader`. pub fn readFileAllocOptions( dir: Dir, - /// On Windows, should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). + /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, should be encoded as valid UTF-8. /// On other platforms, an opaque sequence of bytes with no particular encoding. sub_path: []const u8, @@ -2056,7 +2148,7 @@ pub const DeleteTreeError = error{ InvalidUtf8, /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ + /// https://wtf-8.codeberg.page/ InvalidWtf8, /// On Windows, file paths cannot contain these characters: @@ -2071,7 +2163,7 @@ pub const DeleteTreeError = error{ /// removes it. If it cannot be removed because it is a non-empty directory, /// this function recursively removes its entries and then tries again. /// This operation is not atomic on most file systems. -/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void { @@ -2278,7 +2370,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void { /// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size. /// This is slower than `deleteTree` but uses less stack space. -/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. pub fn deleteTreeMinStackSize(self: Dir, sub_path: []const u8) DeleteTreeError!void { @@ -2476,7 +2568,7 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File pub const WriteFileError = File.WriteError || File.OpenError; pub const WriteFileOptions = struct { - /// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). + /// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. sub_path: []const u8, @@ -2494,7 +2586,7 @@ pub fn writeFile(self: Dir, options: WriteFileOptions) WriteFileError!void { pub const AccessError = posix.AccessError; /// Test accessing `sub_path`. -/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. /// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function. @@ -2549,7 +2641,7 @@ pub const PrevStatus = enum { /// atime, and mode of the source file so that the next call to `updateFile` will not need a copy. /// Returns the previous status of the file before updating. /// If any of the directories do not exist for dest_path, they are created. -/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, both paths should be encoded as valid UTF-8. /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. pub fn updateFile( @@ -2621,7 +2713,7 @@ pub const CopyFileError = File.OpenError || File.StatError || /// dest_path. /// /// On Windows, both paths should be encoded as -/// [WTF-8](https://simonsapin.github.io/wtf-8/). On WASI, both paths should be +/// [WTF-8](https://wtf-8.codeberg.page/). On WASI, both paths should be /// encoded as valid UTF-8. On other platforms, both paths are an opaque /// sequence of bytes with no particular encoding. pub fn copyFile( @@ -2666,7 +2758,7 @@ pub const AtomicFileOptions = struct { /// Always call `AtomicFile.deinit` to clean up, regardless of whether /// `AtomicFile.finish` succeeded. `dest_path` must remain valid until /// `AtomicFile.deinit` is called. -/// On Windows, `dest_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `dest_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `dest_path` should be encoded as valid UTF-8. /// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding. pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile { @@ -2700,7 +2792,7 @@ pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError /// Symlinks are followed. /// /// `sub_path` may be absolute, in which case `self` is ignored. -/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_path` should be encoded as valid UTF-8. /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding. pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat { diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index 99dda27885..169f3f5222 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -60,7 +60,7 @@ pub const OpenError = error{ /// WASI-only; file paths must be valid UTF-8. InvalidUtf8, /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ + /// https://wtf-8.codeberg.page/ InvalidWtf8, /// On Windows, file paths cannot contain these characters: /// '/', '*', '?', '"', '<', '>', '|' @@ -1189,7 +1189,7 @@ pub const Reader = struct { pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void { switch (r.mode) { .positional, .positional_reading => { - setPosAdjustingBuffer(r, @intCast(@as(i64, @intCast(r.pos)) + offset)); + setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset)); }, .streaming, .streaming_reading => { if (posix.SEEK == void) { @@ -1198,7 +1198,7 @@ pub const Reader = struct { } const seek_err = r.seek_err orelse e: { if (posix.lseek_CUR(r.file.handle, offset)) |_| { - setPosAdjustingBuffer(r, @intCast(@as(i64, @intCast(r.pos)) + offset)); + setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset)); return; } else |err| { r.seek_err = err; @@ -1222,16 +1222,17 @@ pub const Reader = struct { pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void { switch (r.mode) { .positional, .positional_reading => { - setPosAdjustingBuffer(r, offset); + setLogicalPos(r, offset); }, .streaming, .streaming_reading => { - if (offset >= r.pos) return Reader.seekBy(r, @intCast(offset - r.pos)); + const logical_pos = logicalPos(r); + if (offset >= logical_pos) return Reader.seekBy(r, @intCast(offset - logical_pos)); if (r.seek_err) |err| return err; posix.lseek_SET(r.file.handle, offset) catch |err| { r.seek_err = err; return err; }; - setPosAdjustingBuffer(r, offset); + setLogicalPos(r, offset); }, .failure => return r.seek_err.?, } @@ -1241,7 +1242,7 @@ pub const Reader = struct { return r.pos - r.interface.bufferedLen(); } - fn setPosAdjustingBuffer(r: *Reader, offset: u64) void { + fn setLogicalPos(r: *Reader, offset: u64) void { const logical_pos = logicalPos(r); if (offset < logical_pos or offset >= r.pos) { r.interface.seek = 0; @@ -1269,13 +1270,15 @@ pub const Reader = struct { }, .positional_reading => { const dest = limit.slice(try w.writableSliceGreedy(1)); - const n = try readPositional(r, dest); + var data: [1][]u8 = .{dest}; + const n = try readVecPositional(r, &data); w.advance(n); return n; }, .streaming_reading => { const dest = limit.slice(try w.writableSliceGreedy(1)); - const n = try readStreaming(r, dest); + var data: [1][]u8 = .{dest}; + const n = try readVecStreaming(r, &data); w.advance(n); return n; }, @@ -1286,92 +1289,98 @@ pub const Reader = struct { fn readVec(io_reader: *std.Io.Reader, data: [][]u8) std.Io.Reader.Error!usize { const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); switch (r.mode) { - .positional, .positional_reading => { - if (is_windows) { - // Unfortunately, `ReadFileScatter` cannot be used since it - // requires page alignment. - if (io_reader.seek == io_reader.end) { - io_reader.seek = 0; - io_reader.end = 0; - } - const first = data[0]; - if (first.len >= io_reader.buffer.len - io_reader.end) { - return readPositional(r, first); - } else { - io_reader.end += try readPositional(r, io_reader.buffer[io_reader.end..]); - return 0; - } - } - var iovecs_buffer: [max_buffers_len]posix.iovec = undefined; - const dest_n, const data_size = try io_reader.writableVectorPosix(&iovecs_buffer, data); - const dest = iovecs_buffer[0..dest_n]; - assert(dest[0].len > 0); - const n = posix.preadv(r.file.handle, dest, r.pos) catch |err| switch (err) { - error.Unseekable => { - r.mode = r.mode.toStreaming(); - const pos = r.pos; - if (pos != 0) { - r.pos = 0; - r.seekBy(@intCast(pos)) catch { - r.mode = .failure; - return error.ReadFailed; - }; - } - return 0; - }, - else => |e| { - r.err = e; + .positional, .positional_reading => return readVecPositional(r, data), + .streaming, .streaming_reading => return readVecStreaming(r, data), + .failure => return error.ReadFailed, + } + } + + fn readVecPositional(r: *Reader, data: [][]u8) std.Io.Reader.Error!usize { + const io_reader = &r.interface; + if (is_windows) { + // Unfortunately, `ReadFileScatter` cannot be used since it + // requires page alignment. + if (io_reader.seek == io_reader.end) { + io_reader.seek = 0; + io_reader.end = 0; + } + const first = data[0]; + if (first.len >= io_reader.buffer.len - io_reader.end) { + return readPositional(r, first); + } else { + io_reader.end += try readPositional(r, io_reader.buffer[io_reader.end..]); + return 0; + } + } + var iovecs_buffer: [max_buffers_len]posix.iovec = undefined; + const dest_n, const data_size = try io_reader.writableVectorPosix(&iovecs_buffer, data); + const dest = iovecs_buffer[0..dest_n]; + assert(dest[0].len > 0); + const n = posix.preadv(r.file.handle, dest, r.pos) catch |err| switch (err) { + error.Unseekable => { + r.mode = r.mode.toStreaming(); + const pos = r.pos; + if (pos != 0) { + r.pos = 0; + r.seekBy(@intCast(pos)) catch { + r.mode = .failure; return error.ReadFailed; - }, - }; - if (n == 0) { - r.size = r.pos; - return error.EndOfStream; - } - r.pos += n; - if (n > data_size) { - io_reader.end += n - data_size; - return data_size; + }; } - return n; + return 0; }, - .streaming, .streaming_reading => { - if (is_windows) { - // Unfortunately, `ReadFileScatter` cannot be used since it - // requires page alignment. - if (io_reader.seek == io_reader.end) { - io_reader.seek = 0; - io_reader.end = 0; - } - const first = data[0]; - if (first.len >= io_reader.buffer.len - io_reader.end) { - return readStreaming(r, first); - } else { - io_reader.end += try readStreaming(r, io_reader.buffer[io_reader.end..]); - return 0; - } - } - var iovecs_buffer: [max_buffers_len]posix.iovec = undefined; - const dest_n, const data_size = try io_reader.writableVectorPosix(&iovecs_buffer, data); - const dest = iovecs_buffer[0..dest_n]; - assert(dest[0].len > 0); - const n = posix.readv(r.file.handle, dest) catch |err| { - r.err = err; - return error.ReadFailed; - }; - if (n == 0) { - r.size = r.pos; - return error.EndOfStream; - } - r.pos += n; - if (n > data_size) { - io_reader.end += n - data_size; - return data_size; - } - return n; + else => |e| { + r.err = e; + return error.ReadFailed; }, - .failure => return error.ReadFailed, + }; + if (n == 0) { + r.size = r.pos; + return error.EndOfStream; + } + r.pos += n; + if (n > data_size) { + io_reader.end += n - data_size; + return data_size; } + return n; + } + + fn readVecStreaming(r: *Reader, data: [][]u8) std.Io.Reader.Error!usize { + const io_reader = &r.interface; + if (is_windows) { + // Unfortunately, `ReadFileScatter` cannot be used since it + // requires page alignment. + if (io_reader.seek == io_reader.end) { + io_reader.seek = 0; + io_reader.end = 0; + } + const first = data[0]; + if (first.len >= io_reader.buffer.len - io_reader.end) { + return readStreaming(r, first); + } else { + io_reader.end += try readStreaming(r, io_reader.buffer[io_reader.end..]); + return 0; + } + } + var iovecs_buffer: [max_buffers_len]posix.iovec = undefined; + const dest_n, const data_size = try io_reader.writableVectorPosix(&iovecs_buffer, data); + const dest = iovecs_buffer[0..dest_n]; + assert(dest[0].len > 0); + const n = posix.readv(r.file.handle, dest) catch |err| { + r.err = err; + return error.ReadFailed; + }; + if (n == 0) { + r.size = r.pos; + return error.EndOfStream; + } + r.pos += n; + if (n > data_size) { + io_reader.end += n - data_size; + return data_size; + } + return n; } fn discard(io_reader: *std.Io.Reader, limit: std.Io.Limit) std.Io.Reader.Error!usize { @@ -1440,7 +1449,7 @@ pub const Reader = struct { } } - pub fn readPositional(r: *Reader, dest: []u8) std.Io.Reader.Error!usize { + fn readPositional(r: *Reader, dest: []u8) std.Io.Reader.Error!usize { const n = r.file.pread(dest, r.pos) catch |err| switch (err) { error.Unseekable => { r.mode = r.mode.toStreaming(); @@ -1467,7 +1476,7 @@ pub const Reader = struct { return n; } - pub fn readStreaming(r: *Reader, dest: []u8) std.Io.Reader.Error!usize { + fn readStreaming(r: *Reader, dest: []u8) std.Io.Reader.Error!usize { const n = r.file.read(dest) catch |err| { r.err = err; return error.ReadFailed; @@ -1480,14 +1489,6 @@ pub const Reader = struct { return n; } - pub fn read(r: *Reader, dest: []u8) std.Io.Reader.Error!usize { - switch (r.mode) { - .positional, .positional_reading => return readPositional(r, dest), - .streaming, .streaming_reading => return readStreaming(r, dest), - .failure => return error.ReadFailed, - } - } - pub fn atEnd(r: *Reader) bool { // Even if stat fails, size is set when end is encountered. const size = r.size orelse return false; @@ -1732,7 +1733,7 @@ pub const Writer = struct { ) std.Io.Writer.FileError!usize { const reader_buffered = file_reader.interface.buffered(); if (reader_buffered.len >= @intFromEnum(limit)) - return sendFileBuffered(io_w, file_reader, reader_buffered); + return sendFileBuffered(io_w, file_reader, limit.slice(reader_buffered)); const writer_buffered = io_w.buffered(); const file_limit = @intFromEnum(limit) - reader_buffered.len; const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); @@ -1804,7 +1805,7 @@ pub const Writer = struct { return error.EndOfStream; } const consumed = io_w.consume(@intCast(sbytes)); - file_reader.seekTo(file_reader.pos + consumed) catch return error.ReadFailed; + file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed; return consumed; } @@ -1865,7 +1866,7 @@ pub const Writer = struct { return error.EndOfStream; } const consumed = io_w.consume(@bitCast(len)); - file_reader.seekTo(file_reader.pos + consumed) catch return error.ReadFailed; + file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed; return consumed; } @@ -1998,7 +1999,7 @@ pub const Writer = struct { reader_buffered: []const u8, ) std.Io.Writer.FileError!usize { const n = try drain(io_w, &.{reader_buffered}, 1); - file_reader.seekTo(file_reader.pos + n) catch return error.ReadFailed; + file_reader.seekBy(@intCast(n)) catch return error.ReadFailed; return n; } diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig index 9f5ed323f1..bc722275a6 100644 --- a/lib/std/fs/path.zig +++ b/lib/std/fs/path.zig @@ -2,7 +2,7 @@ //! //! Windows paths are arbitrary sequences of `u16` (WTF-16). //! For cross-platform APIs that deal with sequences of `u8`, Windows -//! paths are encoded by Zig as [WTF-8](https://simonsapin.github.io/wtf-8/). +//! paths are encoded by Zig as [WTF-8](https://wtf-8.codeberg.page/). //! WTF-8 is a superset of UTF-8 that allows encoding surrogate codepoints, //! which enables lossless roundtripping when converting to/from WTF-16 //! (as long as the WTF-8 encoded surrogate codepoints do not form a pair). diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 65e86e4c2e..0949d71a2d 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1525,6 +1525,41 @@ test "sendfile" { try testing.expectEqualStrings("header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n", written_buf[0..amt]); } +test "sendfile with buffered data" { + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + try tmp.dir.makePath("os_test_tmp"); + + var dir = try tmp.dir.openDir("os_test_tmp", .{}); + defer dir.close(); + + var src_file = try dir.createFile("sendfile1.txt", .{ .read = true }); + defer src_file.close(); + + try src_file.writeAll("AAAABBBB"); + + var dest_file = try dir.createFile("sendfile2.txt", .{ .read = true }); + defer dest_file.close(); + + var src_buffer: [32]u8 = undefined; + var file_reader = src_file.reader(&src_buffer); + + try file_reader.seekTo(0); + try file_reader.interface.fill(8); + + var fallback_buffer: [32]u8 = undefined; + var file_writer = dest_file.writer(&fallback_buffer); + + try std.testing.expectEqual(4, try file_writer.interface.sendFileAll(&file_reader, .limited(4))); + + var written_buf: [8]u8 = undefined; + const amt = try dest_file.preadAll(&written_buf, 0); + + try std.testing.expectEqual(4, amt); + try std.testing.expectEqualSlices(u8, "AAAA", written_buf[0..amt]); +} + test "copyRangeAll" { var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -1765,14 +1800,14 @@ test "walker" { // iteration order of walker is undefined, so need lookup maps to check against - const expected_paths = std.StaticStringMap(void).initComptime(.{ - .{"dir1"}, - .{"dir2"}, - .{"dir3"}, - .{"dir4"}, - .{"dir3" ++ fs.path.sep_str ++ "sub1"}, - .{"dir3" ++ fs.path.sep_str ++ "sub2"}, - .{"dir3" ++ fs.path.sep_str ++ "sub2" ++ fs.path.sep_str ++ "subsub1"}, + const expected_paths = std.StaticStringMap(usize).initComptime(.{ + .{ "dir1", 1 }, + .{ "dir2", 1 }, + .{ "dir3", 1 }, + .{ "dir4", 1 }, + .{ "dir3" ++ fs.path.sep_str ++ "sub1", 2 }, + .{ "dir3" ++ fs.path.sep_str ++ "sub2", 2 }, + .{ "dir3" ++ fs.path.sep_str ++ "sub2" ++ fs.path.sep_str ++ "subsub1", 3 }, }); const expected_basenames = std.StaticStringMap(void).initComptime(.{ @@ -1802,6 +1837,76 @@ test "walker" { std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); return err; }; + testing.expectEqual(expected_paths.get(entry.path).?, entry.depth()) catch |err| { + std.debug.print("path reported unexpected depth: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); + return err; + }; + // make sure that the entry.dir is the containing dir + var entry_dir = try entry.dir.openDir(entry.basename, .{}); + defer entry_dir.close(); + num_walked += 1; + } + try testing.expectEqual(expected_paths.kvs.len, num_walked); +} + +test "selective walker, skip entries that start with ." { + var tmp = tmpDir(.{ .iterate = true }); + defer tmp.cleanup(); + + const paths_to_create: []const []const u8 = &.{ + "dir1/foo/.git/ignored", + ".hidden/bar", + "a/b/c", + "a/baz", + }; + + // iteration order of walker is undefined, so need lookup maps to check against + + const expected_paths = std.StaticStringMap(usize).initComptime(.{ + .{ "dir1", 1 }, + .{ "dir1" ++ fs.path.sep_str ++ "foo", 2 }, + .{ "a", 1 }, + .{ "a" ++ fs.path.sep_str ++ "b", 2 }, + .{ "a" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c", 3 }, + .{ "a" ++ fs.path.sep_str ++ "baz", 2 }, + }); + + const expected_basenames = std.StaticStringMap(void).initComptime(.{ + .{"dir1"}, + .{"foo"}, + .{"a"}, + .{"b"}, + .{"c"}, + .{"baz"}, + }); + + for (paths_to_create) |path| { + try tmp.dir.makePath(path); + } + + var walker = try tmp.dir.walkSelectively(testing.allocator); + defer walker.deinit(); + + var num_walked: usize = 0; + while (try walker.next()) |entry| { + if (entry.basename[0] == '.') continue; + if (entry.kind == .directory) { + try walker.enter(entry); + } + + testing.expect(expected_basenames.has(entry.basename)) catch |err| { + std.debug.print("found unexpected basename: {f}\n", .{std.ascii.hexEscape(entry.basename, .lower)}); + return err; + }; + testing.expect(expected_paths.has(entry.path)) catch |err| { + std.debug.print("found unexpected path: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); + return err; + }; + testing.expectEqual(expected_paths.get(entry.path).?, entry.depth()) catch |err| { + std.debug.print("path reported unexpected depth: {f}\n", .{std.ascii.hexEscape(entry.path, .lower)}); + return err; + }; + // make sure that the entry.dir is the containing dir var entry_dir = try entry.dir.openDir(entry.basename, .{}); defer entry_dir.close(); @@ -2180,3 +2285,34 @@ test "seekTo flushes buffered data" { try file_reader.interface.readSliceAll(&buf); try std.testing.expectEqualStrings(contents, &buf); } + +test "File.Writer sendfile with buffered contents" { + var tmp_dir = testing.tmpDir(.{}); + defer tmp_dir.cleanup(); + + { + try tmp_dir.dir.writeFile(.{ .sub_path = "a", .data = "bcd" }); + const in = try tmp_dir.dir.openFile("a", .{}); + defer in.close(); + const out = try tmp_dir.dir.createFile("b", .{}); + defer out.close(); + + var in_buf: [2]u8 = undefined; + var in_r = in.reader(&in_buf); + _ = try in_r.getSize(); // Catch seeks past end by populating size + try in_r.interface.fill(2); + + var out_buf: [1]u8 = undefined; + var out_w = out.writerStreaming(&out_buf); + try out_w.interface.writeByte('a'); + try testing.expectEqual(3, try out_w.interface.sendFileAll(&in_r, .unlimited)); + try out_w.interface.flush(); + } + + var check = try tmp_dir.dir.openFile("b", .{}); + defer check.close(); + var check_buf: [4]u8 = undefined; + var check_r = check.reader(&check_buf); + try testing.expectEqualStrings("abcd", try check_r.interface.take(4)); + try testing.expectError(error.EndOfStream, check_r.interface.takeByte()); +} diff --git a/lib/std/heap.zig b/lib/std/heap.zig index 84f119d3f9..b5fd822959 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -78,13 +78,15 @@ pub fn defaultQueryPageSize() usize { }; var size = global.cached_result.load(.unordered); if (size > 0) return size; - size = switch (builtin.os.tag) { - .linux => if (builtin.link_libc) @intCast(std.c.sysconf(@intFromEnum(std.c._SC.PAGESIZE))) else std.os.linux.getauxval(std.elf.AT_PAGESZ), - .driverkit, .ios, .macos, .tvos, .visionos, .watchos => blk: { + size = size: switch (builtin.os.tag) { + .linux => if (builtin.link_libc) + @max(std.c.sysconf(@intFromEnum(std.c._SC.PAGESIZE)), 0) + else + std.os.linux.getauxval(std.elf.AT_PAGESZ), + .driverkit, .ios, .macos, .tvos, .visionos, .watchos => { const task_port = std.c.mach_task_self(); // mach_task_self may fail "if there are any resource failures or other errors". - if (task_port == std.c.TASK.NULL) - break :blk 0; + if (task_port == std.c.TASK.NULL) break :size 0; var info_count = std.c.TASK.VM.INFO_COUNT; var vm_info: std.c.task_vm_info_data_t = undefined; vm_info.page_size = 0; @@ -94,21 +96,28 @@ pub fn defaultQueryPageSize() usize { @as(std.c.task_info_t, @ptrCast(&vm_info)), &info_count, ); - assert(vm_info.page_size != 0); - break :blk @intCast(vm_info.page_size); + break :size @intCast(vm_info.page_size); }, - .windows => blk: { - var info: std.os.windows.SYSTEM_INFO = undefined; - std.os.windows.kernel32.GetSystemInfo(&info); - break :blk info.dwPageSize; + .windows => { + var sbi: windows.SYSTEM_BASIC_INFORMATION = undefined; + switch (windows.ntdll.NtQuerySystemInformation( + .SystemBasicInformation, + &sbi, + @sizeOf(windows.SYSTEM_BASIC_INFORMATION), + null, + )) { + .SUCCESS => break :size sbi.PageSize, + else => break :size 0, + } }, else => if (builtin.link_libc) - @intCast(std.c.sysconf(@intFromEnum(std.c._SC.PAGESIZE))) + @max(std.c.sysconf(@intFromEnum(std.c._SC.PAGESIZE)), 0) else if (builtin.os.tag == .freestanding or builtin.os.tag == .other) @compileError("unsupported target: freestanding/other") else @compileError("pageSize on " ++ @tagName(builtin.cpu.arch) ++ "-" ++ @tagName(builtin.os.tag) ++ " is not supported without linking libc, using the default implementation"), }; + if (size == 0) size = page_size_max; assert(size >= page_size_min); assert(size <= page_size_max); diff --git a/lib/std/heap/PageAllocator.zig b/lib/std/heap/PageAllocator.zig index 106460387a..f3e3857b58 100644 --- a/lib/std/heap/PageAllocator.zig +++ b/lib/std/heap/PageAllocator.zig @@ -183,7 +183,7 @@ pub fn realloc(uncasted_memory: []u8, new_len: usize, may_move: bool) ?[*]u8 { if (posix.MREMAP != void) { // TODO: if the next_mmap_addr_hint is within the remapped range, update it - const new_memory = posix.mremap(memory.ptr, memory.len, new_len, .{ .MAYMOVE = may_move }, null) catch return null; + const new_memory = posix.mremap(memory.ptr, page_aligned_len, new_size_aligned, .{ .MAYMOVE = may_move }, null) catch return null; return new_memory.ptr; } diff --git a/lib/std/heap/debug_allocator.zig b/lib/std/heap/debug_allocator.zig index a4b1de5b47..fe512f03d6 100644 --- a/lib/std/heap/debug_allocator.zig +++ b/lib/std/heap/debug_allocator.zig @@ -505,23 +505,14 @@ pub fn DebugAllocator(comptime config: Config) type { return if (leaks) .leak else .ok; } - fn collectStackTrace(first_trace_addr: usize, addresses: *[stack_n]usize) void { - if (stack_n == 0) return; - @memset(addresses, 0); - var stack_trace: StackTrace = .{ - .instruction_addresses = addresses, - .index = 0, - }; - std.debug.captureStackTrace(first_trace_addr, &stack_trace); + fn collectStackTrace(first_trace_addr: usize, addr_buf: *[stack_n]usize) void { + const st = std.debug.captureCurrentStackTrace(.{ .first_address = first_trace_addr }, addr_buf); + @memset(addr_buf[@min(st.index, addr_buf.len)..], 0); } fn reportDoubleFree(ret_addr: usize, alloc_stack_trace: StackTrace, free_stack_trace: StackTrace) void { - var addresses: [stack_n]usize = @splat(0); - var second_free_stack_trace: StackTrace = .{ - .instruction_addresses = &addresses, - .index = 0, - }; - std.debug.captureStackTrace(ret_addr, &second_free_stack_trace); + var addr_buf: [stack_n]usize = undefined; + const second_free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf); log.err("Double free detected. Allocation: {f} First free: {f} Second free: {f}", .{ alloc_stack_trace, free_stack_trace, second_free_stack_trace, }); @@ -562,12 +553,8 @@ pub fn DebugAllocator(comptime config: Config) type { } if (config.safety and old_mem.len != entry.value_ptr.bytes.len) { - var addresses: [stack_n]usize = [1]usize{0} ** stack_n; - var free_stack_trace: StackTrace = .{ - .instruction_addresses = &addresses, - .index = 0, - }; - std.debug.captureStackTrace(ret_addr, &free_stack_trace); + var addr_buf: [stack_n]usize = undefined; + const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf); log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ entry.value_ptr.bytes.len, old_mem.len, @@ -672,12 +659,8 @@ pub fn DebugAllocator(comptime config: Config) type { } if (config.safety and old_mem.len != entry.value_ptr.bytes.len) { - var addresses: [stack_n]usize = [1]usize{0} ** stack_n; - var free_stack_trace = StackTrace{ - .instruction_addresses = &addresses, - .index = 0, - }; - std.debug.captureStackTrace(ret_addr, &free_stack_trace); + var addr_buf: [stack_n]usize = undefined; + const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf); log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ entry.value_ptr.bytes.len, old_mem.len, @@ -900,12 +883,8 @@ pub fn DebugAllocator(comptime config: Config) type { if (requested_size == 0) @panic("Invalid free"); const slot_alignment = bucket.log2PtrAligns(slot_count)[slot_index]; if (old_memory.len != requested_size or alignment != slot_alignment) { - var addresses: [stack_n]usize = [1]usize{0} ** stack_n; - var free_stack_trace: StackTrace = .{ - .instruction_addresses = &addresses, - .index = 0, - }; - std.debug.captureStackTrace(return_address, &free_stack_trace); + var addr_buf: [stack_n]usize = undefined; + const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf); if (old_memory.len != requested_size) { log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ requested_size, @@ -999,12 +978,8 @@ pub fn DebugAllocator(comptime config: Config) type { if (requested_size == 0) @panic("Invalid free"); const slot_alignment = bucket.log2PtrAligns(slot_count)[slot_index]; if (memory.len != requested_size or alignment != slot_alignment) { - var addresses: [stack_n]usize = [1]usize{0} ** stack_n; - var free_stack_trace: StackTrace = .{ - .instruction_addresses = &addresses, - .index = 0, - }; - std.debug.captureStackTrace(return_address, &free_stack_trace); + var addr_buf: [stack_n]usize = undefined; + const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf); if (memory.len != requested_size) { log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{ requested_size, diff --git a/lib/std/http/test.zig b/lib/std/http/test.zig index 91a57dbbe2..fac241ede4 100644 --- a/lib/std/http/test.zig +++ b/lib/std/http/test.zig @@ -1105,8 +1105,8 @@ fn createTestServer(S: type) !*TestServer { const test_server = try std.testing.allocator.create(TestServer); test_server.* = .{ .net_server = try address.listen(.{ .reuse_address = true }), - .server_thread = try std.Thread.spawn(.{}, S.run, .{test_server}), .shutting_down = false, + .server_thread = try std.Thread.spawn(.{}, S.run, .{test_server}), }; return test_server; } diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 455f5f8fb1..d541e2d13e 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -839,62 +839,75 @@ pub const nlist = extern struct { pub const nlist_64 = extern struct { n_strx: u32, - n_type: u8, + n_type: packed union { + bits: packed struct(u8) { + ext: bool, + type: enum(u3) { + undf = 0, + abs = 1, + sect = 7, + pbud = 6, + indr = 5, + _, + }, + pext: bool, + /// Any non-zero value indicates this is an stab, so the `stab` field should be used. + is_stab: u3, + }, + stab: enum(u8) { + gsym = N_GSYM, + fname = N_FNAME, + fun = N_FUN, + stsym = N_STSYM, + lcsym = N_LCSYM, + bnsym = N_BNSYM, + ast = N_AST, + opt = N_OPT, + rsym = N_RSYM, + sline = N_SLINE, + ensym = N_ENSYM, + ssym = N_SSYM, + so = N_SO, + oso = N_OSO, + lsym = N_LSYM, + bincl = N_BINCL, + sol = N_SOL, + params = N_PARAMS, + version = N_VERSION, + olevel = N_OLEVEL, + psym = N_PSYM, + eincl = N_EINCL, + entry = N_ENTRY, + lbrac = N_LBRAC, + excl = N_EXCL, + rbrac = N_RBRAC, + bcomm = N_BCOMM, + ecomm = N_ECOMM, + ecoml = N_ECOML, + leng = N_LENG, + _, + }, + }, n_sect: u8, - n_desc: u16, + n_desc: packed struct(u16) { + _pad0: u3 = 0, + arm_thumb_def: bool, + referenced_dynamically: bool, + /// The meaning of this bit is contextual. + /// See `N_DESC_DISCARDED` and `N_NO_DEAD_STRIP`. + discarded_or_no_dead_strip: bool, + weak_ref: bool, + /// The meaning of this bit is contextual. + /// See `N_WEAK_DEF` and `N_REF_TO_WEAK`. + weak_def_or_ref_to_weak: bool, + symbol_resolver: bool, + alt_entry: bool, + _pad2: u6 = 0, + }, n_value: u64, - pub fn stab(sym: nlist_64) bool { - return N_STAB & sym.n_type != 0; - } - - pub fn pext(sym: nlist_64) bool { - return N_PEXT & sym.n_type != 0; - } - - pub fn ext(sym: nlist_64) bool { - return N_EXT & sym.n_type != 0; - } - - pub fn sect(sym: nlist_64) bool { - const type_ = N_TYPE & sym.n_type; - return type_ == N_SECT; - } - - pub fn undf(sym: nlist_64) bool { - const type_ = N_TYPE & sym.n_type; - return type_ == N_UNDF; - } - - pub fn indr(sym: nlist_64) bool { - const type_ = N_TYPE & sym.n_type; - return type_ == N_INDR; - } - - pub fn abs(sym: nlist_64) bool { - const type_ = N_TYPE & sym.n_type; - return type_ == N_ABS; - } - - pub fn weakDef(sym: nlist_64) bool { - return sym.n_desc & N_WEAK_DEF != 0; - } - - pub fn weakRef(sym: nlist_64) bool { - return sym.n_desc & N_WEAK_REF != 0; - } - - pub fn discarded(sym: nlist_64) bool { - return sym.n_desc & N_DESC_DISCARDED != 0; - } - - pub fn noDeadStrip(sym: nlist_64) bool { - return sym.n_desc & N_NO_DEAD_STRIP != 0; - } - pub fn tentative(sym: nlist_64) bool { - if (!sym.undf()) return false; - return sym.n_value != 0; + return sym.n_type.bits.type == .undf and sym.n_value != 0; } }; @@ -2046,7 +2059,7 @@ pub const unwind_info_compressed_second_level_page_header = extern struct { // encodings array }; -pub const UnwindInfoCompressedEntry = packed struct { +pub const UnwindInfoCompressedEntry = packed struct(u32) { funcOffset: u24, encodingIndex: u8, }; diff --git a/lib/std/math/isnan.zig b/lib/std/math/isnan.zig index 7cd8c2e734..b3de93597a 100644 --- a/lib/std/math/isnan.zig +++ b/lib/std/math/isnan.zig @@ -28,6 +28,8 @@ test isNan { } test isSignalNan { + if (builtin.zig_backend == .stage2_x86_64 and builtin.object_format == .coff and builtin.abi != .gnu) return error.SkipZigTest; + inline for ([_]type{ f16, f32, f64, f80, f128, c_longdouble }) |T| { // TODO: Signalling NaN values get converted to quiet NaN values in // some cases where they shouldn't such that this can fail. diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 3cd0507bee..ed48132d9a 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -1704,6 +1704,40 @@ test count { try testing.expect(count(u8, "owowowu", "owowu") == 1); } +/// Returns the number of needles inside the haystack +pub fn countScalar(comptime T: type, haystack: []const T, needle: T) usize { + const n = haystack.len; + var i: usize = 0; + var found: usize = 0; + + if (use_vectors_for_comparison and + (@typeInfo(T) == .int or @typeInfo(T) == .float) and std.math.isPowerOfTwo(@bitSizeOf(T))) + { + if (std.simd.suggestVectorLength(T)) |block_size| { + const Block = @Vector(block_size, T); + + const letter_mask: Block = @splat(needle); + while (n - i >= block_size) : (i += block_size) { + const haystack_block: Block = haystack[i..][0..block_size].*; + found += std.simd.countTrues(letter_mask == haystack_block); + } + } + } + + for (haystack[i..n]) |item| { + found += @intFromBool(item == needle); + } + + return found; +} + +test countScalar { + try testing.expectEqual(0, countScalar(u8, "", 'h')); + try testing.expectEqual(1, countScalar(u8, "h", 'h')); + try testing.expectEqual(2, countScalar(u8, "hh", 'h')); + try testing.expectEqual(3, countScalar(u8, " abcabc abc", 'b')); +} + /// Returns true if the haystack contains expected_count or more needles /// needle.len must be > 0 /// does not count overlapping needles diff --git a/lib/std/net.zig b/lib/std/net.zig index 37fe2734d5..9d70515c2e 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -1396,7 +1396,7 @@ fn parseHosts( br: *Io.Reader, ) error{ OutOfMemory, ReadFailed }!void { while (true) { - const line = br.takeDelimiterExclusive('\n') catch |err| switch (err) { + const line = br.takeDelimiter('\n') catch |err| switch (err) { error.StreamTooLong => { // Skip lines that are too long. _ = br.discardDelimiterInclusive('\n') catch |e| switch (e) { @@ -1406,7 +1406,8 @@ fn parseHosts( continue; }, error.ReadFailed => return error.ReadFailed, - error.EndOfStream => break, + } orelse { + break; // end of stream }; var split_it = mem.splitScalar(u8, line, '#'); const no_comment_line = split_it.first(); diff --git a/lib/std/os.zig b/lib/std/os.zig index b0aaa47ac8..f2519a62e9 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -98,7 +98,7 @@ pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool { /// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is /// unsupported on WASI. /// -/// * On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// * On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// * On other platforms, the result is an opaque sequence of bytes with no particular encoding. /// /// Calling this function is usually a bug. diff --git a/lib/std/os/emscripten.zig b/lib/std/os/emscripten.zig index 0e07a8afb7..1ecb4f6bb0 100644 --- a/lib/std/os/emscripten.zig +++ b/lib/std/os/emscripten.zig @@ -400,28 +400,6 @@ pub const timeval = extern struct { usec: i32, }; -pub const REG = struct { - pub const GS = 0; - pub const FS = 1; - pub const ES = 2; - pub const DS = 3; - pub const EDI = 4; - pub const ESI = 5; - pub const EBP = 6; - pub const ESP = 7; - pub const EBX = 8; - pub const EDX = 9; - pub const ECX = 10; - pub const EAX = 11; - pub const TRAPNO = 12; - pub const ERR = 13; - pub const EIP = 14; - pub const CS = 15; - pub const EFL = 16; - pub const UESP = 17; - pub const SS = 18; -}; - pub const S = struct { pub const IFMT = 0o170000; @@ -813,13 +791,6 @@ pub const dl_phdr_info = extern struct { phnum: u16, }; -pub const mcontext_t = extern struct { - gregs: [19]usize, - fpregs: [*]u8, - oldmask: usize, - cr2: usize, -}; - pub const msghdr = std.c.msghdr; pub const msghdr_const = std.c.msghdr; @@ -846,15 +817,6 @@ pub const timezone = extern struct { dsttime: i32, }; -pub const ucontext_t = extern struct { - flags: usize, - link: ?*ucontext_t, - stack: stack_t, - mcontext: mcontext_t, - sigmask: sigset_t, - regspace: [28]usize, -}; - pub const utsname = extern struct { sysname: [64:0]u8, nodename: [64:0]u8, diff --git a/lib/std/os/freebsd.zig b/lib/std/os/freebsd.zig index 4c68405c22..2d082bf0cd 100644 --- a/lib/std/os/freebsd.zig +++ b/lib/std/os/freebsd.zig @@ -3,6 +3,7 @@ const fd_t = std.c.fd_t; const off_t = std.c.off_t; const unexpectedErrno = std.posix.unexpectedErrno; const errno = std.posix.errno; +const builtin = @import("builtin"); pub const CopyFileRangeError = std.posix.UnexpectedError || error{ /// If infd is not open for reading or outfd is not open for writing, or diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 48e9f08d5d..502b2defc2 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -47,10 +47,7 @@ const arch_bits = switch (native_arch) { .powerpc, .powerpcle => @import("linux/powerpc.zig"), .powerpc64, .powerpc64le => @import("linux/powerpc64.zig"), .s390x => @import("linux/s390x.zig"), - else => struct { - pub const ucontext_t = void; - pub const getcontext = {}; - }, + else => struct {}, }; const syscall_bits = if (native_arch.isThumb()) @import("linux/thumb.zig") else arch_bits; @@ -95,7 +92,6 @@ pub const Elf_Symndx = arch_bits.Elf_Symndx; pub const F = arch_bits.F; pub const Flock = arch_bits.Flock; pub const HWCAP = arch_bits.HWCAP; -pub const REG = arch_bits.REG; pub const SC = arch_bits.SC; pub const Stat = arch_bits.Stat; pub const VDSO = arch_bits.VDSO; @@ -103,16 +99,13 @@ pub const blkcnt_t = arch_bits.blkcnt_t; pub const blksize_t = arch_bits.blksize_t; pub const dev_t = arch_bits.dev_t; pub const ino_t = arch_bits.ino_t; -pub const mcontext_t = arch_bits.mcontext_t; pub const mode_t = arch_bits.mode_t; pub const nlink_t = arch_bits.nlink_t; pub const off_t = arch_bits.off_t; pub const time_t = arch_bits.time_t; pub const timeval = arch_bits.timeval; pub const timezone = arch_bits.timezone; -pub const ucontext_t = arch_bits.ucontext_t; pub const user_desc = arch_bits.user_desc; -pub const getcontext = arch_bits.getcontext; pub const tls = @import("linux/tls.zig"); pub const BPF = @import("linux/bpf.zig"); @@ -3616,8 +3609,7 @@ pub const PROT = struct { pub const EXEC = 0x4; /// page may be used for atomic ops pub const SEM = switch (native_arch) { - // TODO: also xtensa - .mips, .mipsel, .mips64, .mips64el => 0x10, + .mips, .mipsel, .mips64, .mips64el, .xtensa => 0x10, else => 0x8, }; /// mprotect flag: extend change to start of growsdown vma diff --git a/lib/std/os/linux/aarch64.zig b/lib/std/os/linux/aarch64.zig index 5fcf04c58b..4888a9eda3 100644 --- a/lib/std/os/linux/aarch64.zig +++ b/lib/std/os/linux/aarch64.zig @@ -241,26 +241,4 @@ pub const timezone = extern struct { dsttime: i32, }; -pub const mcontext_t = extern struct { - fault_address: usize, - regs: [31]usize, - sp: usize, - pc: usize, - pstate: usize, - // Make sure the field is correctly aligned since this area - // holds various FP/vector registers - reserved1: [256 * 16]u8 align(16), -}; - -pub const ucontext_t = extern struct { - flags: usize, - link: ?*ucontext_t, - stack: stack_t, - sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask - mcontext: mcontext_t, -}; - -/// TODO -pub const getcontext = {}; - pub const Elf_Symndx = u32; diff --git a/lib/std/os/linux/arm.zig b/lib/std/os/linux/arm.zig index 5f41607efe..7995570654 100644 --- a/lib/std/os/linux/arm.zig +++ b/lib/std/os/linux/arm.zig @@ -277,40 +277,4 @@ pub const timezone = extern struct { dsttime: i32, }; -pub const mcontext_t = extern struct { - trap_no: usize, - error_code: usize, - oldmask: usize, - arm_r0: usize, - arm_r1: usize, - arm_r2: usize, - arm_r3: usize, - arm_r4: usize, - arm_r5: usize, - arm_r6: usize, - arm_r7: usize, - arm_r8: usize, - arm_r9: usize, - arm_r10: usize, - arm_fp: usize, - arm_ip: usize, - arm_sp: usize, - arm_lr: usize, - arm_pc: usize, - arm_cpsr: usize, - fault_address: usize, -}; - -pub const ucontext_t = extern struct { - flags: usize, - link: ?*ucontext_t, - stack: stack_t, - mcontext: mcontext_t, - sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask - regspace: [64]u64, -}; - -/// TODO -pub const getcontext = {}; - pub const Elf_Symndx = u32; diff --git a/lib/std/os/linux/hexagon.zig b/lib/std/os/linux/hexagon.zig index 255faba20f..e3bf7a8709 100644 --- a/lib/std/os/linux/hexagon.zig +++ b/lib/std/os/linux/hexagon.zig @@ -9,6 +9,8 @@ const gid_t = std.os.linux.gid_t; const pid_t = std.os.linux.pid_t; const sockaddr = linux.sockaddr; const socklen_t = linux.socklen_t; +const stack_t = linux.stack_t; +const sigset_t = linux.sigset_t; const timespec = std.os.linux.timespec; pub fn syscall0(number: SYS) usize { @@ -166,30 +168,6 @@ pub const Flock = extern struct { __unused: [4]u8, }; -pub const msghdr = extern struct { - name: ?*sockaddr, - namelen: socklen_t, - iov: [*]iovec, - iovlen: i32, - __pad1: i32 = 0, - control: ?*anyopaque, - controllen: socklen_t, - __pad2: socklen_t = 0, - flags: i32, -}; - -pub const msghdr_const = extern struct { - name: ?*const sockaddr, - namelen: socklen_t, - iov: [*]const iovec_const, - iovlen: i32, - __pad1: i32 = 0, - control: ?*const anyopaque, - controllen: socklen_t, - __pad2: socklen_t = 0, - flags: i32, -}; - pub const blksize_t = i32; pub const nlink_t = u32; pub const time_t = i32; @@ -234,9 +212,3 @@ pub const Stat = extern struct { pub const Elf_Symndx = u32; pub const VDSO = void; - -/// TODO -pub const ucontext_t = void; - -/// TODO -pub const getcontext = {}; diff --git a/lib/std/os/linux/loongarch64.zig b/lib/std/os/linux/loongarch64.zig index b6ff38fccd..4ed817167d 100644 --- a/lib/std/os/linux/loongarch64.zig +++ b/lib/std/os/linux/loongarch64.zig @@ -135,30 +135,6 @@ pub fn clone() callconv(.naked) usize { ); } -pub const msghdr = extern struct { - name: ?*sockaddr, - namelen: socklen_t, - iov: [*]iovec, - iovlen: i32, - __pad1: i32 = 0, - control: ?*anyopaque, - controllen: socklen_t, - __pad2: socklen_t = 0, - flags: i32, -}; - -pub const msghdr_const = extern struct { - name: ?*const sockaddr, - namelen: socklen_t, - iov: [*]const iovec_const, - iovlen: i32, - __pad1: i32 = 0, - control: ?*const anyopaque, - controllen: socklen_t, - __pad2: socklen_t = 0, - flags: i32, -}; - pub const blksize_t = i32; pub const nlink_t = u32; pub const time_t = i64; @@ -234,22 +210,4 @@ pub const VDSO = struct { pub const CGT_VER = "LINUX_5.10"; }; -pub const mcontext_t = extern struct { - pc: u64, - regs: [32]u64, - flags: u32, - extcontext: [0]u64 align(16), -}; - -pub const ucontext_t = extern struct { - flags: c_ulong, - link: ?*ucontext_t, - stack: stack_t, - sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask - mcontext: mcontext_t, -}; - pub const Elf_Symndx = u32; - -/// TODO -pub const getcontext = {}; diff --git a/lib/std/os/linux/m68k.zig b/lib/std/os/linux/m68k.zig index 78851c3928..c3bd42b2ff 100644 --- a/lib/std/os/linux/m68k.zig +++ b/lib/std/os/linux/m68k.zig @@ -199,27 +199,6 @@ pub const Flock = extern struct { pid: pid_t, }; -// TODO: not 100% sure of padding for msghdr -pub const msghdr = extern struct { - name: ?*sockaddr, - namelen: socklen_t, - iov: [*]iovec, - iovlen: i32, - control: ?*anyopaque, - controllen: socklen_t, - flags: i32, -}; - -pub const msghdr_const = extern struct { - name: ?*const sockaddr, - namelen: socklen_t, - iov: [*]const iovec_const, - iovlen: i32, - control: ?*const anyopaque, - controllen: socklen_t, - flags: i32, -}; - pub const Stat = extern struct { dev: dev_t, __pad: i16, @@ -255,9 +234,3 @@ pub const Elf_Symndx = u32; // No VDSO used as of glibc 112a0ae18b831bf31f44d81b82666980312511d6. pub const VDSO = void; - -/// TODO -pub const ucontext_t = void; - -/// TODO -pub const getcontext = {}; diff --git a/lib/std/os/linux/mips.zig b/lib/std/os/linux/mips.zig index c971bbd739..6412c847bd 100644 --- a/lib/std/os/linux/mips.zig +++ b/lib/std/os/linux/mips.zig @@ -9,6 +9,8 @@ const iovec_const = std.posix.iovec_const; const uid_t = linux.uid_t; const gid_t = linux.gid_t; const pid_t = linux.pid_t; +const stack_t = linux.stack_t; +const sigset_t = linux.sigset_t; const sockaddr = linux.sockaddr; const timespec = linux.timespec; @@ -346,9 +348,3 @@ pub const timezone = extern struct { }; pub const Elf_Symndx = u32; - -/// TODO -pub const ucontext_t = void; - -/// TODO -pub const getcontext = {}; diff --git a/lib/std/os/linux/mips64.zig b/lib/std/os/linux/mips64.zig index 91fcacef38..4419190193 100644 --- a/lib/std/os/linux/mips64.zig +++ b/lib/std/os/linux/mips64.zig @@ -9,6 +9,8 @@ const iovec_const = std.posix.iovec_const; const uid_t = linux.uid_t; const gid_t = linux.gid_t; const pid_t = linux.pid_t; +const stack_t = linux.stack_t; +const sigset_t = linux.sigset_t; const sockaddr = linux.sockaddr; const timespec = linux.timespec; @@ -325,9 +327,3 @@ pub const timezone = extern struct { }; pub const Elf_Symndx = u32; - -/// TODO -pub const ucontext_t = void; - -/// TODO -pub const getcontext = {}; diff --git a/lib/std/os/linux/powerpc.zig b/lib/std/os/linux/powerpc.zig index 3e876bb3f0..c96a8a0804 100644 --- a/lib/std/os/linux/powerpc.zig +++ b/lib/std/os/linux/powerpc.zig @@ -351,36 +351,4 @@ pub const timezone = extern struct { dsttime: i32, }; -pub const greg_t = u32; -pub const gregset_t = [48]greg_t; -pub const fpregset_t = [33]f64; - -pub const vrregset = extern struct { - vrregs: [32][4]u32, - vrsave: u32, - _pad: [2]u32, - vscr: u32, -}; -pub const vrregset_t = vrregset; - -pub const mcontext_t = extern struct { - gp_regs: gregset_t, - fp_regs: fpregset_t, - v_regs: vrregset_t align(16), -}; - -pub const ucontext_t = extern struct { - flags: u32, - link: ?*ucontext_t, - stack: stack_t, - pad: [7]i32, - regs: *mcontext_t, - sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask - pad2: [3]i32, - mcontext: mcontext_t, -}; - pub const Elf_Symndx = u32; - -/// TODO -pub const getcontext = {}; diff --git a/lib/std/os/linux/powerpc64.zig b/lib/std/os/linux/powerpc64.zig index 70b11a86bb..5b1af7cc2b 100644 --- a/lib/std/os/linux/powerpc64.zig +++ b/lib/std/os/linux/powerpc64.zig @@ -336,46 +336,4 @@ pub const timezone = extern struct { dsttime: i32, }; -pub const greg_t = u64; -pub const gregset_t = [48]greg_t; -pub const fpregset_t = [33]f64; - -/// The position of the vscr register depends on endianness. -/// On C, macros are used to change vscr_word's offset to -/// account for this. Here we'll just define vscr_word_le -/// and vscr_word_be. Code must take care to use the correct one. -pub const vrregset = extern struct { - vrregs: [32][4]u32 align(16), - vscr_word_le: u32, - _pad1: [2]u32, - vscr_word_be: u32, - vrsave: u32, - _pad2: [3]u32, -}; -pub const vrregset_t = vrregset; - -pub const mcontext_t = extern struct { - __unused: [4]u64, - signal: i32, - _pad0: i32, - handler: u64, - oldmask: u64, - regs: ?*anyopaque, - gp_regs: gregset_t, - fp_regs: fpregset_t, - v_regs: *vrregset_t, - vmx_reserve: [34 + 34 + 32 + 1]i64, -}; - -pub const ucontext_t = extern struct { - flags: u32, - link: ?*ucontext_t, - stack: stack_t, - sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask - mcontext: mcontext_t, -}; - pub const Elf_Symndx = u32; - -/// TODO -pub const getcontext = {}; diff --git a/lib/std/os/linux/riscv32.zig b/lib/std/os/linux/riscv32.zig index 01b6002135..a0f73adeef 100644 --- a/lib/std/os/linux/riscv32.zig +++ b/lib/std/os/linux/riscv32.zig @@ -220,41 +220,3 @@ pub const VDSO = struct { pub const CGT_SYM = "__vdso_clock_gettime"; pub const CGT_VER = "LINUX_4.15"; }; - -pub const f_ext_state = extern struct { - f: [32]f32, - fcsr: u32, -}; - -pub const d_ext_state = extern struct { - f: [32]f64, - fcsr: u32, -}; - -pub const q_ext_state = extern struct { - f: [32]f128, - fcsr: u32, - _reserved: [3]u32, -}; - -pub const fpstate = extern union { - f: f_ext_state, - d: d_ext_state, - q: q_ext_state, -}; - -pub const mcontext_t = extern struct { - gregs: [32]u32, - fpregs: fpstate, -}; - -pub const ucontext_t = extern struct { - flags: c_ulong, - link: ?*ucontext_t, - stack: stack_t, - sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask - mcontext: mcontext_t, -}; - -/// TODO -pub const getcontext = {}; diff --git a/lib/std/os/linux/riscv64.zig b/lib/std/os/linux/riscv64.zig index 577cf3ec48..5331620451 100644 --- a/lib/std/os/linux/riscv64.zig +++ b/lib/std/os/linux/riscv64.zig @@ -220,41 +220,3 @@ pub const VDSO = struct { pub const CGT_SYM = "__vdso_clock_gettime"; pub const CGT_VER = "LINUX_4.15"; }; - -pub const f_ext_state = extern struct { - f: [32]f32, - fcsr: u32, -}; - -pub const d_ext_state = extern struct { - f: [32]f64, - fcsr: u32, -}; - -pub const q_ext_state = extern struct { - f: [32]f128, - fcsr: u32, - _reserved: [3]u32, -}; - -pub const fpstate = extern union { - f: f_ext_state, - d: d_ext_state, - q: q_ext_state, -}; - -pub const mcontext_t = extern struct { - gregs: [32]u64, - fpregs: fpstate, -}; - -pub const ucontext_t = extern struct { - flags: c_ulong, - link: ?*ucontext_t, - stack: stack_t, - sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask - mcontext: mcontext_t, -}; - -/// TODO -pub const getcontext = {}; diff --git a/lib/std/os/linux/s390x.zig b/lib/std/os/linux/s390x.zig index 620aedfb69..00bc09c518 100644 --- a/lib/std/os/linux/s390x.zig +++ b/lib/std/os/linux/s390x.zig @@ -199,30 +199,6 @@ pub const Flock = extern struct { pid: pid_t, }; -pub const msghdr = extern struct { - name: ?*sockaddr, - namelen: socklen_t, - iov: [*]iovec, - __pad1: i32 = 0, - iovlen: i32, - control: ?*anyopaque, - __pad2: i32 = 0, - controllen: socklen_t, - flags: i32, -}; - -pub const msghdr_const = extern struct { - name: ?*const sockaddr, - namelen: socklen_t, - iov: [*]const iovec_const, - __pad1: i32 = 0, - iovlen: i32, - control: ?*const anyopaque, - __pad2: i32 = 0, - controllen: socklen_t, - flags: i32, -}; - // The `stat` definition used by the Linux kernel. pub const Stat = extern struct { dev: dev_t, @@ -259,20 +235,3 @@ pub const VDSO = struct { pub const CGT_SYM = "__kernel_clock_gettime"; pub const CGT_VER = "LINUX_2.6.29"; }; - -pub const ucontext_t = extern struct { - flags: u64, - link: ?*ucontext_t, - stack: stack_t, - mcontext: mcontext_t, - sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask -}; - -pub const mcontext_t = extern struct { - __regs1: [18]u64, - __regs2: [18]u32, - __regs3: [16]f64, -}; - -/// TODO -pub const getcontext = {}; diff --git a/lib/std/os/linux/sparc64.zig b/lib/std/os/linux/sparc64.zig index 1377343888..b542c3f985 100644 --- a/lib/std/os/linux/sparc64.zig +++ b/lib/std/os/linux/sparc64.zig @@ -54,7 +54,7 @@ pub fn syscall_fork() usize { \\ 2: : [ret] "={o0}" (-> usize), : [number] "{g1}" (@intFromEnum(SYS.fork)), - : .{ .memory = true, .xcc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true }); + : .{ .memory = true, .icc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true }); } pub fn syscall0(number: SYS) usize { @@ -66,7 +66,7 @@ pub fn syscall0(number: SYS) usize { \\ 1: : [ret] "={o0}" (-> usize), : [number] "{g1}" (@intFromEnum(number)), - : .{ .memory = true, .xcc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true }); + : .{ .memory = true, .icc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true }); } pub fn syscall1(number: SYS, arg1: usize) usize { @@ -79,7 +79,7 @@ pub fn syscall1(number: SYS, arg1: usize) usize { : [ret] "={o0}" (-> usize), : [number] "{g1}" (@intFromEnum(number)), [arg1] "{o0}" (arg1), - : .{ .memory = true, .xcc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true }); + : .{ .memory = true, .icc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true }); } pub fn syscall2(number: SYS, arg1: usize, arg2: usize) usize { @@ -93,7 +93,7 @@ pub fn syscall2(number: SYS, arg1: usize, arg2: usize) usize { : [number] "{g1}" (@intFromEnum(number)), [arg1] "{o0}" (arg1), [arg2] "{o1}" (arg2), - : .{ .memory = true, .xcc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true }); + : .{ .memory = true, .icc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true }); } pub fn syscall3(number: SYS, arg1: usize, arg2: usize, arg3: usize) usize { @@ -108,7 +108,7 @@ pub fn syscall3(number: SYS, arg1: usize, arg2: usize, arg3: usize) usize { [arg1] "{o0}" (arg1), [arg2] "{o1}" (arg2), [arg3] "{o2}" (arg3), - : .{ .memory = true, .xcc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true }); + : .{ .memory = true, .icc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true }); } pub fn syscall4(number: SYS, arg1: usize, arg2: usize, arg3: usize, arg4: usize) usize { @@ -124,7 +124,7 @@ pub fn syscall4(number: SYS, arg1: usize, arg2: usize, arg3: usize, arg4: usize) [arg2] "{o1}" (arg2), [arg3] "{o2}" (arg3), [arg4] "{o3}" (arg4), - : .{ .memory = true, .xcc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true }); + : .{ .memory = true, .icc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true }); } pub fn syscall5(number: SYS, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) usize { @@ -141,7 +141,7 @@ pub fn syscall5(number: SYS, arg1: usize, arg2: usize, arg3: usize, arg4: usize, [arg3] "{o2}" (arg3), [arg4] "{o3}" (arg4), [arg5] "{o4}" (arg5), - : .{ .memory = true, .xcc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true }); + : .{ .memory = true, .icc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true }); } pub fn syscall6( @@ -167,7 +167,7 @@ pub fn syscall6( [arg4] "{o3}" (arg4), [arg5] "{o4}" (arg5), [arg6] "{o5}" (arg6), - : .{ .memory = true, .xcc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true }); + : .{ .memory = true, .icc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true }); } pub fn clone() callconv(.naked) usize { @@ -233,7 +233,7 @@ pub fn restore_rt() callconv(.c) void { return asm volatile ("t 0x6d" : : [number] "{g1}" (@intFromEnum(SYS.rt_sigreturn)), - : .{ .memory = true, .xcc = true, .o0 = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true }); + : .{ .memory = true, .icc = true, .o0 = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true }); } pub const F = struct { @@ -325,107 +325,4 @@ pub const timezone = extern struct { dsttime: i32, }; -// TODO I'm not sure if the code below is correct, need someone with more -// knowledge about sparc64 linux internals to look into. - pub const Elf_Symndx = u32; - -pub const fpstate = extern struct { - regs: [32]u64, - fsr: u64, - gsr: u64, - fprs: u64, -}; - -pub const __fpq = extern struct { - fpq_addr: *u32, - fpq_instr: u32, -}; - -pub const __fq = extern struct { - FQu: extern union { - whole: f64, - fpq: __fpq, - }, -}; - -pub const fpregset_t = extern struct { - fpu_fr: extern union { - fpu_regs: [32]u32, - fpu_dregs: [32]f64, - fpu_qregs: [16]c_longdouble, - }, - fpu_q: *__fq, - fpu_fsr: u64, - fpu_qcnt: u8, - fpu_q_entrysize: u8, - fpu_en: u8, -}; - -pub const siginfo_fpu_t = extern struct { - float_regs: [64]u32, - fsr: u64, - gsr: u64, - fprs: u64, -}; - -pub const sigcontext = extern struct { - info: [128]i8, - regs: extern struct { - u_regs: [16]u64, - tstate: u64, - tpc: u64, - tnpc: u64, - y: u64, - fprs: u64, - }, - fpu_save: *siginfo_fpu_t, - stack: extern struct { - sp: usize, - flags: i32, - size: u64, - }, - mask: u64, -}; - -pub const greg_t = u64; -pub const gregset_t = [19]greg_t; - -pub const fq = extern struct { - addr: *u64, - insn: u32, -}; - -pub const fpu_t = extern struct { - fregs: extern union { - sregs: [32]u32, - dregs: [32]u64, - qregs: [16]c_longdouble, - }, - fsr: u64, - fprs: u64, - gsr: u64, - fq: *fq, - qcnt: u8, - qentsz: u8, - enab: u8, -}; - -pub const mcontext_t = extern struct { - gregs: gregset_t, - fp: greg_t, - i7: greg_t, - fpregs: fpu_t, -}; - -pub const ucontext_t = extern struct { - link: ?*ucontext_t, - flags: u64, - sigmask: u64, - mcontext: mcontext_t, - stack: stack_t, - sigset: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask -}; - -/// TODO -pub const getcontext = {}; diff --git a/lib/std/os/linux/x86.zig b/lib/std/os/linux/x86.zig index 0ffe9c2956..c24ffcae6a 100644 --- a/lib/std/os/linux/x86.zig +++ b/lib/std/os/linux/x86.zig @@ -80,28 +80,32 @@ pub fn syscall6( arg5: usize, arg6: usize, ) usize { - // arg5/arg6 are passed via memory as we're out of registers if ebp is used as frame pointer, or - // if we're compiling with PIC. We push arg5/arg6 on the stack before changing ebp/esp as the - // compiler may reference arg5/arg6 as an offset relative to ebp/esp. + // arg6 can't be passed to asm in a register because ebp might be reserved as the frame pointer + // and there are no more GPRs available; so we'll need a memory operand for it. Adding that + // memory operand means that on PIC we might need a reference to the GOT, which in turn needs + // *its* own GPR, so we need to pass another arg in memory too! This is surprisingly hard to get + // right, because we can't touch esp or ebp until we're done with the memory input (as that + // input could be relative to esp or ebp). + const args56: [2]usize = .{ arg5, arg6 }; return asm volatile ( - \\ push %[arg5] - \\ push %[arg6] - \\ push %%edi + \\ push %[args56] \\ push %%ebp - \\ mov 12(%%esp), %%edi - \\ mov 8(%%esp), %%ebp + \\ mov 4(%%esp), %%ebp + \\ mov %%edi, 4(%%esp) + \\ // The saved %edi and %ebp are on the stack, and %ebp points to `args56`. + \\ // Prepare the last two args, syscall, then pop the saved %ebp and %edi. + \\ mov (%%ebp), %%edi + \\ mov 4(%%ebp), %%ebp \\ int $0x80 \\ pop %%ebp \\ pop %%edi - \\ add $8, %%esp : [ret] "={eax}" (-> usize), : [number] "{eax}" (@intFromEnum(number)), [arg1] "{ebx}" (arg1), [arg2] "{ecx}" (arg2), [arg3] "{edx}" (arg3), [arg4] "{esi}" (arg4), - [arg5] "rm" (arg5), - [arg6] "rm" (arg6), + [args56] "rm" (&args56), : .{ .memory = true }); } @@ -284,44 +288,6 @@ pub const timezone = extern struct { dsttime: i32, }; -pub const mcontext_t = extern struct { - gregs: [19]usize, - fpregs: [*]u8, - oldmask: usize, - cr2: usize, -}; - -pub const REG = struct { - pub const GS = 0; - pub const FS = 1; - pub const ES = 2; - pub const DS = 3; - pub const EDI = 4; - pub const ESI = 5; - pub const EBP = 6; - pub const ESP = 7; - pub const EBX = 8; - pub const EDX = 9; - pub const ECX = 10; - pub const EAX = 11; - pub const TRAPNO = 12; - pub const ERR = 13; - pub const EIP = 14; - pub const CS = 15; - pub const EFL = 16; - pub const UESP = 17; - pub const SS = 18; -}; - -pub const ucontext_t = extern struct { - flags: usize, - link: ?*ucontext_t, - stack: stack_t, - mcontext: mcontext_t, - sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask - regspace: [64]u64, -}; - pub const Elf_Symndx = u32; pub const user_desc = extern struct { @@ -362,87 +328,3 @@ pub const SC = struct { pub const recvmmsg = 19; pub const sendmmsg = 20; }; - -fn gpRegisterOffset(comptime reg_index: comptime_int) usize { - return @offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "gregs") + @sizeOf(usize) * reg_index; -} - -noinline fn getContextReturnAddress() usize { - return @returnAddress(); -} - -pub fn getContextInternal() callconv(.naked) usize { - asm volatile ( - \\ movl $0, %[flags_offset:c](%%edx) - \\ movl $0, %[link_offset:c](%%edx) - \\ movl %%edi, %[edi_offset:c](%%edx) - \\ movl %%esi, %[esi_offset:c](%%edx) - \\ movl %%ebp, %[ebp_offset:c](%%edx) - \\ movl %%ebx, %[ebx_offset:c](%%edx) - \\ movl %%edx, %[edx_offset:c](%%edx) - \\ movl %%ecx, %[ecx_offset:c](%%edx) - \\ movl %%eax, %[eax_offset:c](%%edx) - \\ movl (%%esp), %%ecx - \\ movl %%ecx, %[eip_offset:c](%%edx) - \\ leal 4(%%esp), %%ecx - \\ movl %%ecx, %[esp_offset:c](%%edx) - \\ xorl %%ecx, %%ecx - \\ movw %%fs, %%cx - \\ movl %%ecx, %[fs_offset:c](%%edx) - \\ leal %[regspace_offset:c](%%edx), %%ecx - \\ movl %%ecx, %[fpregs_offset:c](%%edx) - \\ fnstenv (%%ecx) - \\ fldenv (%%ecx) - \\ pushl %%ebx - \\ pushl %%esi - \\ xorl %%ebx, %%ebx - \\ movl %[sigaltstack], %%eax - \\ leal %[stack_offset:c](%%edx), %%ecx - \\ int $0x80 - \\ testl %%eax, %%eax - \\ jnz 0f - \\ movl %[sigprocmask], %%eax - \\ xorl %%ecx, %%ecx - \\ leal %[sigmask_offset:c](%%edx), %%edx - \\ movl %[sigset_size], %%esi - \\ int $0x80 - \\0: - \\ popl %%esi - \\ popl %%ebx - \\ retl - : - : [flags_offset] "i" (@offsetOf(ucontext_t, "flags")), - [link_offset] "i" (@offsetOf(ucontext_t, "link")), - [edi_offset] "i" (comptime gpRegisterOffset(REG.EDI)), - [esi_offset] "i" (comptime gpRegisterOffset(REG.ESI)), - [ebp_offset] "i" (comptime gpRegisterOffset(REG.EBP)), - [esp_offset] "i" (comptime gpRegisterOffset(REG.ESP)), - [ebx_offset] "i" (comptime gpRegisterOffset(REG.EBX)), - [edx_offset] "i" (comptime gpRegisterOffset(REG.EDX)), - [ecx_offset] "i" (comptime gpRegisterOffset(REG.ECX)), - [eax_offset] "i" (comptime gpRegisterOffset(REG.EAX)), - [eip_offset] "i" (comptime gpRegisterOffset(REG.EIP)), - [fs_offset] "i" (comptime gpRegisterOffset(REG.FS)), - [fpregs_offset] "i" (@offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "fpregs")), - [regspace_offset] "i" (@offsetOf(ucontext_t, "regspace")), - [sigaltstack] "i" (@intFromEnum(linux.SYS.sigaltstack)), - [stack_offset] "i" (@offsetOf(ucontext_t, "stack")), - [sigprocmask] "i" (@intFromEnum(linux.SYS.rt_sigprocmask)), - [sigmask_offset] "i" (@offsetOf(ucontext_t, "sigmask")), - [sigset_size] "i" (linux.NSIG / 8), - : .{ .cc = true, .memory = true, .eax = true, .ecx = true, .edx = true }); -} - -pub inline fn getcontext(context: *ucontext_t) usize { - // This method is used so that getContextInternal can control - // its prologue in order to read ESP from a constant offset. - // An aligned stack is not needed for getContextInternal. - var clobber_edx: usize = undefined; - return asm volatile ( - \\ calll %[getContextInternal:P] - : [_] "={eax}" (-> usize), - [_] "={edx}" (clobber_edx), - : [_] "{edx}" (context), - [getContextInternal] "X" (&getContextInternal), - : .{ .cc = true, .memory = true, .ecx = true }); -} diff --git a/lib/std/os/linux/x86_64.zig b/lib/std/os/linux/x86_64.zig index 583fad872d..e3db9e99c4 100644 --- a/lib/std/os/linux/x86_64.zig +++ b/lib/std/os/linux/x86_64.zig @@ -190,32 +190,6 @@ pub const ARCH = struct { pub const GET_GS = 0x1004; }; -pub const REG = struct { - pub const R8 = 0; - pub const R9 = 1; - pub const R10 = 2; - pub const R11 = 3; - pub const R12 = 4; - pub const R13 = 5; - pub const R14 = 6; - pub const R15 = 7; - pub const RDI = 8; - pub const RSI = 9; - pub const RBP = 10; - pub const RBX = 11; - pub const RDX = 12; - pub const RAX = 13; - pub const RCX = 14; - pub const RSP = 15; - pub const RIP = 16; - pub const EFL = 17; - pub const CSGSFS = 18; - pub const ERR = 19; - pub const TRAPNO = 20; - pub const OLDMASK = 21; - pub const CR2 = 22; -}; - pub const Flock = extern struct { type: i16, whence: i16, @@ -272,178 +246,3 @@ pub const timezone = extern struct { }; pub const Elf_Symndx = u32; - -pub const greg_t = usize; -pub const gregset_t = [23]greg_t; -pub const fpstate = extern struct { - cwd: u16, - swd: u16, - ftw: u16, - fop: u16, - rip: usize, - rdp: usize, - mxcsr: u32, - mxcr_mask: u32, - st: [8]extern struct { - significand: [4]u16, - exponent: u16, - padding: [3]u16 = undefined, - }, - xmm: [16]extern struct { - element: [4]u32, - }, - padding: [24]u32 = undefined, -}; -pub const fpregset_t = *fpstate; -pub const sigcontext = extern struct { - r8: usize, - r9: usize, - r10: usize, - r11: usize, - r12: usize, - r13: usize, - r14: usize, - r15: usize, - - rdi: usize, - rsi: usize, - rbp: usize, - rbx: usize, - rdx: usize, - rax: usize, - rcx: usize, - rsp: usize, - rip: usize, - eflags: usize, - - cs: u16, - gs: u16, - fs: u16, - pad0: u16 = undefined, - - err: usize, - trapno: usize, - oldmask: usize, - cr2: usize, - - fpstate: *fpstate, - reserved1: [8]usize = undefined, -}; - -pub const mcontext_t = extern struct { - gregs: gregset_t, - fpregs: fpregset_t, - reserved1: [8]usize = undefined, -}; - -/// ucontext_t is part of the state pushed on the stack by the kernel for -/// a signal handler. And also a subset of the state returned from the -/// makecontext/getcontext/swapcontext POSIX APIs. -/// -/// Currently this structure matches the glibc/musl layout. It contains a -/// 1024-bit signal mask, and `fpregs_mem`. This structure should be -/// split into one for the kernel ABI and c.zig should define a glibc/musl -/// compatible structure. -pub const ucontext_t = extern struct { - flags: usize, - link: ?*ucontext_t, - stack: stack_t, - mcontext: mcontext_t, - sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a glibc-compatible (1024-bit) sigmask. - fpregs_mem: [64]usize, // Not part of kernel ABI, only part of glibc ucontext_t -}; - -fn gpRegisterOffset(comptime reg_index: comptime_int) usize { - return @offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "gregs") + @sizeOf(usize) * reg_index; -} - -fn getContextInternal() callconv(.naked) usize { - // TODO: Read GS/FS registers? - asm volatile ( - \\ movq $0, %[flags_offset:c](%%rdi) - \\ movq $0, %[link_offset:c](%%rdi) - \\ movq %%r8, %[r8_offset:c](%%rdi) - \\ movq %%r9, %[r9_offset:c](%%rdi) - \\ movq %%r10, %[r10_offset:c](%%rdi) - \\ movq %%r11, %[r11_offset:c](%%rdi) - \\ movq %%r12, %[r12_offset:c](%%rdi) - \\ movq %%r13, %[r13_offset:c](%%rdi) - \\ movq %%r14, %[r14_offset:c](%%rdi) - \\ movq %%r15, %[r15_offset:c](%%rdi) - \\ movq %%rdi, %[rdi_offset:c](%%rdi) - \\ movq %%rsi, %[rsi_offset:c](%%rdi) - \\ movq %%rbp, %[rbp_offset:c](%%rdi) - \\ movq %%rbx, %[rbx_offset:c](%%rdi) - \\ movq %%rdx, %[rdx_offset:c](%%rdi) - \\ movq %%rax, %[rax_offset:c](%%rdi) - \\ movq %%rcx, %[rcx_offset:c](%%rdi) - \\ movq (%%rsp), %%rcx - \\ movq %%rcx, %[rip_offset:c](%%rdi) - \\ leaq 8(%%rsp), %%rcx - \\ movq %%rcx, %[rsp_offset:c](%%rdi) - \\ pushfq - \\ popq %[efl_offset:c](%%rdi) - \\ leaq %[fpmem_offset:c](%%rdi), %%rcx - \\ movq %%rcx, %[fpstate_offset:c](%%rdi) - \\ fnstenv (%%rcx) - \\ fldenv (%%rcx) - \\ stmxcsr %[mxcsr_offset:c](%%rdi) - \\ leaq %[stack_offset:c](%%rdi), %%rsi - \\ movq %%rdi, %%r8 - \\ xorl %%edi, %%edi - \\ movl %[sigaltstack], %%eax - \\ syscall - \\ testq %%rax, %%rax - \\ jnz 0f - \\ movl %[sigprocmask], %%eax - \\ xorl %%esi, %%esi - \\ leaq %[sigmask_offset:c](%%r8), %%rdx - \\ movl %[sigset_size], %%r10d - \\ syscall - \\0: - \\ retq - : - : [flags_offset] "i" (@offsetOf(ucontext_t, "flags")), - [link_offset] "i" (@offsetOf(ucontext_t, "link")), - [r8_offset] "i" (comptime gpRegisterOffset(REG.R8)), - [r9_offset] "i" (comptime gpRegisterOffset(REG.R9)), - [r10_offset] "i" (comptime gpRegisterOffset(REG.R10)), - [r11_offset] "i" (comptime gpRegisterOffset(REG.R11)), - [r12_offset] "i" (comptime gpRegisterOffset(REG.R12)), - [r13_offset] "i" (comptime gpRegisterOffset(REG.R13)), - [r14_offset] "i" (comptime gpRegisterOffset(REG.R14)), - [r15_offset] "i" (comptime gpRegisterOffset(REG.R15)), - [rdi_offset] "i" (comptime gpRegisterOffset(REG.RDI)), - [rsi_offset] "i" (comptime gpRegisterOffset(REG.RSI)), - [rbp_offset] "i" (comptime gpRegisterOffset(REG.RBP)), - [rbx_offset] "i" (comptime gpRegisterOffset(REG.RBX)), - [rdx_offset] "i" (comptime gpRegisterOffset(REG.RDX)), - [rax_offset] "i" (comptime gpRegisterOffset(REG.RAX)), - [rcx_offset] "i" (comptime gpRegisterOffset(REG.RCX)), - [rsp_offset] "i" (comptime gpRegisterOffset(REG.RSP)), - [rip_offset] "i" (comptime gpRegisterOffset(REG.RIP)), - [efl_offset] "i" (comptime gpRegisterOffset(REG.EFL)), - [fpstate_offset] "i" (@offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "fpregs")), - [fpmem_offset] "i" (@offsetOf(ucontext_t, "fpregs_mem")), - [mxcsr_offset] "i" (@offsetOf(ucontext_t, "fpregs_mem") + @offsetOf(fpstate, "mxcsr")), - [sigaltstack] "i" (@intFromEnum(linux.SYS.sigaltstack)), - [stack_offset] "i" (@offsetOf(ucontext_t, "stack")), - [sigprocmask] "i" (@intFromEnum(linux.SYS.rt_sigprocmask)), - [sigmask_offset] "i" (@offsetOf(ucontext_t, "sigmask")), - [sigset_size] "i" (@sizeOf(sigset_t)), - : .{ .cc = true, .memory = true, .rax = true, .rcx = true, .rdx = true, .rdi = true, .rsi = true, .r8 = true, .r10 = true, .r11 = true }); -} - -pub inline fn getcontext(context: *ucontext_t) usize { - // This method is used so that getContextInternal can control - // its prologue in order to read RSP from a constant offset - // An aligned stack is not needed for getContextInternal. - var clobber_rdi: usize = undefined; - return asm volatile ( - \\ callq %[getContextInternal:P] - : [_] "={rax}" (-> usize), - [_] "={rdi}" (clobber_rdi), - : [_] "{rdi}" (context), - [getContextInternal] "X" (&getContextInternal), - : .{ .cc = true, .memory = true, .rcx = true, .rdx = true, .rsi = true, .r8 = true, .r10 = true, .r11 = true }); -} diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 66583d21b2..932e401758 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -749,7 +749,7 @@ pub const GetCurrentDirectoryError = error{ }; /// The result is a slice of `buffer`, indexed from 0. -/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/). pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 { var wtf16le_buf: [PATH_MAX_WIDE:0]u16 = undefined; const result = kernel32.GetCurrentDirectoryW(wtf16le_buf.len + 1, &wtf16le_buf); @@ -976,7 +976,7 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin } /// Asserts that there is enough space is `out_buffer`. -/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/). fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 { const win32_namespace_path = path: { if (is_relative) break :path path; @@ -1912,6 +1912,7 @@ pub const CreateProcessError = error{ NameTooLong, InvalidExe, SystemResources, + FileBusy, Unexpected, }; @@ -1982,6 +1983,7 @@ pub fn CreateProcessW( .INVALID_PARAMETER => unreachable, .INVALID_NAME => return error.InvalidName, .FILENAME_EXCED_RANGE => return error.NameTooLong, + .SHARING_VIOLATION => return error.FileBusy, // These are all the system errors that are mapped to ENOEXEC by // the undocumented _dosmaperr (old CRT) or __acrt_errno_map_os_error // (newer CRT) functions. Their code can be found in crt/src/dosmap.c (old SDK) @@ -2416,13 +2418,13 @@ pub const Wtf8ToPrefixedFileWError = error{InvalidWtf8} || Wtf16ToPrefixedFileWE /// Same as `sliceToPrefixedFileW` but accepts a pointer /// to a null-terminated WTF-8 encoded path. -/// https://simonsapin.github.io/wtf-8/ +/// https://wtf-8.codeberg.page/ pub fn cStrToPrefixedFileW(dir: ?HANDLE, s: [*:0]const u8) Wtf8ToPrefixedFileWError!PathSpace { return sliceToPrefixedFileW(dir, mem.sliceTo(s, 0)); } /// Same as `wToPrefixedFileW` but accepts a WTF-8 encoded path. -/// https://simonsapin.github.io/wtf-8/ +/// https://wtf-8.codeberg.page/ pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) Wtf8ToPrefixedFileWError!PathSpace { var temp_path: PathSpace = undefined; temp_path.len = try std.unicode.wtf8ToWtf16Le(&temp_path.data, path); @@ -2849,7 +2851,7 @@ pub fn unexpectedError(err: Win32Error) UnexpectedError { std.debug.print("error.Unexpected: GetLastError({d}): {f}\n", .{ err, std.unicode.fmtUtf16Le(buf_wstr[0..len]), }); - std.debug.dumpCurrentStackTrace(@returnAddress()); + std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() }); } return error.Unexpected; } @@ -2863,7 +2865,7 @@ pub fn unexpectedWSAError(err: ws2_32.WinsockError) UnexpectedError { pub fn unexpectedStatus(status: NTSTATUS) UnexpectedError { if (std.posix.unexpected_error_tracing) { std.debug.print("error.Unexpected NTSTATUS=0x{x}\n", .{@intFromEnum(status)}); - std.debug.dumpCurrentStackTrace(@returnAddress()); + std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() }); } return error.Unexpected; } diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 67d337a081..c05015c304 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -47,7 +47,6 @@ else switch (native_os) { .linux => linux, .plan9 => std.os.plan9, else => struct { - pub const ucontext_t = void; pub const pid_t = void; pub const pollfd = void; pub const fd_t = void; @@ -98,7 +97,6 @@ pub const POLL = system.POLL; pub const POSIX_FADV = system.POSIX_FADV; pub const PR = system.PR; pub const PROT = system.PROT; -pub const REG = system.REG; pub const RLIM = system.RLIM; pub const RR = system.RR; pub const S = system.S; @@ -141,7 +139,6 @@ pub const in_pktinfo = system.in_pktinfo; pub const in6_pktinfo = system.in6_pktinfo; pub const ino_t = system.ino_t; pub const linger = system.linger; -pub const mcontext_t = system.mcontext_t; pub const mode_t = system.mode_t; pub const msghdr = system.msghdr; pub const msghdr_const = system.msghdr_const; @@ -170,7 +167,6 @@ pub const timespec = system.timespec; pub const timestamp_t = system.timestamp_t; pub const timeval = system.timeval; pub const timezone = system.timezone; -pub const ucontext_t = system.ucontext_t; pub const uid_t = system.uid_t; pub const user_desc = system.user_desc; pub const utsname = system.utsname; @@ -692,7 +688,7 @@ pub fn abort() noreturn { // even when linking libc on Windows we use our own abort implementation. // See https://github.com/ziglang/zig/issues/2071 for more details. if (native_os == .windows) { - if (builtin.mode == .Debug) { + if (builtin.mode == .Debug and windows.peb().BeingDebugged != 0) { @breakpoint(); } windows.kernel32.ExitProcess(3); @@ -1628,7 +1624,7 @@ pub const OpenError = error{ InvalidUtf8, /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ + /// https://wtf-8.codeberg.page/ InvalidWtf8, /// On Windows, `\\server` or `\\server\share` was not found. @@ -1651,7 +1647,7 @@ pub const OpenError = error{ } || UnexpectedError; /// Open and possibly create a file. Keeps trying if it gets interrupted. -/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `file_path` should be encoded as valid UTF-8. /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. /// See also `openZ`. @@ -1666,7 +1662,7 @@ pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t { } /// Open and possibly create a file. Keeps trying if it gets interrupted. -/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `file_path` should be encoded as valid UTF-8. /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. /// See also `open`. @@ -1714,7 +1710,7 @@ pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t { /// Open and possibly create a file. Keeps trying if it gets interrupted. /// `file_path` is relative to the open directory handle `dir_fd`. -/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `file_path` should be encoded as valid UTF-8. /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. /// See also `openatZ`. @@ -1837,7 +1833,7 @@ fn openOptionsFromFlagsWasi(oflag: O) OpenError!WasiOpenOptions { /// Open and possibly create a file. Keeps trying if it gets interrupted. /// `file_path` is relative to the open directory handle `dir_fd`. -/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `file_path` should be encoded as valid UTF-8. /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. /// See also `openat`. @@ -2141,7 +2137,7 @@ pub const SymLinkError = error{ InvalidUtf8, /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ + /// https://wtf-8.codeberg.page/ InvalidWtf8, BadPathName, @@ -2150,7 +2146,7 @@ pub const SymLinkError = error{ /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent /// one; the latter case is known as a dangling link. -/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, both paths should be encoded as valid UTF-8. /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. /// If `sym_link_path` exists, it will not be overwritten. @@ -2202,7 +2198,7 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin /// `target_path` **relative** to `newdirfd` directory handle. /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent /// one; the latter case is known as a dangling link. -/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, both paths should be encoded as valid UTF-8. /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. /// If `sym_link_path` exists, it will not be overwritten. @@ -2450,7 +2446,7 @@ pub const UnlinkError = error{ InvalidUtf8, /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ + /// https://wtf-8.codeberg.page/ InvalidWtf8, /// On Windows, file paths cannot contain these characters: @@ -2462,7 +2458,7 @@ pub const UnlinkError = error{ } || UnexpectedError; /// Delete a name and possibly the file it refers to. -/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `file_path` should be encoded as valid UTF-8. /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. /// See also `unlinkZ`. @@ -2526,7 +2522,7 @@ pub const UnlinkatError = UnlinkError || error{ }; /// Delete a file name and possibly the file it refers to, based on an open directory handle. -/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `file_path` should be encoded as valid UTF-8. /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. /// Asserts that the path parameter has no null bytes. @@ -2641,7 +2637,7 @@ pub const RenameError = error{ /// WASI-only; file paths must be valid UTF-8. InvalidUtf8, /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ + /// https://wtf-8.codeberg.page/ InvalidWtf8, BadPathName, NoDevice, @@ -2658,7 +2654,7 @@ pub const RenameError = error{ } || UnexpectedError; /// Change the name or location of a file. -/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, both paths should be encoded as valid UTF-8. /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { @@ -2720,7 +2716,7 @@ pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!v } /// Change the name or location of a file based on an open directory handle. -/// On Windows, both paths should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, both paths should be encoded as valid UTF-8. /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. pub fn renameat( @@ -2828,7 +2824,7 @@ pub fn renameatZ( } /// Same as `renameat` but Windows-only and the path parameters are -/// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded. +/// [WTF-16](https://wtf-8.codeberg.page/#potentially-ill-formed-utf-16) encoded. pub fn renameatW( old_dir_fd: fd_t, old_path_w: []const u16, @@ -2934,7 +2930,7 @@ pub fn renameatW( } } -/// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `sub_dir_path` should be encoded as valid UTF-8. /// On other platforms, `sub_dir_path` is an opaque sequence of bytes with no particular encoding. pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: mode_t) MakeDirError!void { @@ -3044,7 +3040,7 @@ pub const MakeDirError = error{ /// WASI-only; file paths must be valid UTF-8. InvalidUtf8, /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ + /// https://wtf-8.codeberg.page/ InvalidWtf8, BadPathName, NoDevice, @@ -3054,7 +3050,7 @@ pub const MakeDirError = error{ /// Create a directory. /// `mode` is ignored on Windows and WASI. -/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `dir_path` should be encoded as valid UTF-8. /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. pub fn mkdir(dir_path: []const u8, mode: mode_t) MakeDirError!void { @@ -3070,7 +3066,7 @@ pub fn mkdir(dir_path: []const u8, mode: mode_t) MakeDirError!void { } /// Same as `mkdir` but the parameter is null-terminated. -/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `dir_path` should be encoded as valid UTF-8. /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. pub fn mkdirZ(dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void { @@ -3136,7 +3132,7 @@ pub const DeleteDirError = error{ /// WASI-only; file paths must be valid UTF-8. InvalidUtf8, /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ + /// https://wtf-8.codeberg.page/ InvalidWtf8, BadPathName, /// On Windows, `\\server` or `\\server\share` was not found. @@ -3144,7 +3140,7 @@ pub const DeleteDirError = error{ } || UnexpectedError; /// Deletes an empty directory. -/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `dir_path` should be encoded as valid UTF-8. /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. pub fn rmdir(dir_path: []const u8) DeleteDirError!void { @@ -3164,7 +3160,7 @@ pub fn rmdir(dir_path: []const u8) DeleteDirError!void { } /// Same as `rmdir` except the parameter is null-terminated. -/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `dir_path` should be encoded as valid UTF-8. /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void { @@ -3217,12 +3213,12 @@ pub const ChangeCurDirError = error{ /// WASI-only; file paths must be valid UTF-8. InvalidUtf8, /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ + /// https://wtf-8.codeberg.page/ InvalidWtf8, } || UnexpectedError; /// Changes the current working directory of the calling process. -/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `dir_path` should be encoded as valid UTF-8. /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { @@ -3242,7 +3238,7 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { } /// Same as `chdir` except the parameter is null-terminated. -/// On Windows, `dir_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `dir_path` should be encoded as valid UTF-8. /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void { @@ -3319,7 +3315,7 @@ pub const ReadLinkError = error{ /// WASI-only; file paths must be valid UTF-8. InvalidUtf8, /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ + /// https://wtf-8.codeberg.page/ InvalidWtf8, BadPathName, /// Windows-only. This error may occur if the opened reparse point is @@ -3330,11 +3326,11 @@ pub const ReadLinkError = error{ } || UnexpectedError; /// Read value of a symbolic link. -/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `file_path` should be encoded as valid UTF-8. /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. /// The return value is a slice of `out_buffer` from index 0. -/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, the result is encoded as UTF-8. /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { @@ -3350,7 +3346,7 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { } /// Windows-only. Same as `readlink` except `file_path` is WTF16 LE encoded. -/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// See also `readlinkZ`. pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 { return windows.ReadLink(fs.cwd().fd, file_path, out_buffer); @@ -3385,11 +3381,11 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 } /// Similar to `readlink` except reads value of a symbolink link **relative** to `dirfd` directory handle. -/// On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, `file_path` should be encoded as valid UTF-8. /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. /// The return value is a slice of `out_buffer` from index 0. -/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On WASI, the result is encoded as UTF-8. /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. /// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`. @@ -3427,7 +3423,7 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read } /// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 LE encoded. -/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// See also `readlinkat`. pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 { return windows.ReadLink(dirfd, file_path, out_buffer); @@ -4995,7 +4991,9 @@ pub fn munmap(memory: []align(page_size_min) const u8) void { .SUCCESS => return, .INVAL => unreachable, // Invalid parameters. .NOMEM => unreachable, // Attempted to unmap a region in the middle of an existing mapping. - else => unreachable, + else => |e| if (unexpected_error_tracing) { + std.debug.panic("unexpected errno: {d} ({t})", .{ @intFromEnum(e), e }); + } else unreachable, } } @@ -5062,13 +5060,13 @@ pub const AccessError = error{ /// WASI-only; file paths must be valid UTF-8. InvalidUtf8, /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ + /// https://wtf-8.codeberg.page/ InvalidWtf8, } || UnexpectedError; /// check user's permissions for a file /// -/// * On Windows, asserts `path` is valid [WTF-8](https://simonsapin.github.io/wtf-8/). +/// * On Windows, asserts `path` is valid [WTF-8](https://wtf-8.codeberg.page/). /// * On WASI, invalid UTF-8 passed to `path` causes `error.InvalidUtf8`. /// * On other platforms, `path` is an opaque sequence of bytes with no particular encoding. /// @@ -5119,7 +5117,7 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void { /// Check user's permissions for a file, based on an open directory handle. /// -/// * On Windows, asserts `path` is valid [WTF-8](https://simonsapin.github.io/wtf-8/). +/// * On Windows, asserts `path` is valid [WTF-8](https://wtf-8.codeberg.page/). /// * On WASI, invalid UTF-8 passed to `path` causes `error.InvalidUtf8`. /// * On other platforms, `path` is an opaque sequence of bytes with no particular encoding. /// @@ -5639,7 +5637,7 @@ pub const RealPathError = error{ PipeBusy, /// Windows-only; file paths provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ + /// https://wtf-8.codeberg.page/ InvalidWtf8, /// On Windows, `\\server` or `\\server\share` was not found. @@ -5664,7 +5662,7 @@ pub const RealPathError = error{ /// Expands all symbolic links and resolves references to `.`, `..`, and /// extra `/` characters in `pathname`. /// -/// On Windows, `pathname` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, `pathname` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). /// /// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding. /// @@ -5672,14 +5670,18 @@ pub const RealPathError = error{ /// /// See also `realpathZ` and `realpathW`. /// -/// * On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// * On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// * On other platforms, the result is an opaque sequence of bytes with no particular encoding. /// /// Calling this function is usually a bug. pub fn realpath(pathname: []const u8, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 { if (native_os == .windows) { - const pathname_w = try windows.sliceToPrefixedFileW(null, pathname); - return realpathW(pathname_w.span(), out_buffer); + var pathname_w = try windows.sliceToPrefixedFileW(null, pathname); + + const wide_slice = try realpathW2(pathname_w.span(), &pathname_w.data); + + const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); + return out_buffer[0..end_index]; } else if (native_os == .wasi and !builtin.link_libc) { @compileError("WASI does not support os.realpath"); } @@ -5692,8 +5694,12 @@ pub fn realpath(pathname: []const u8, out_buffer: *[max_path_bytes]u8) RealPathE /// Calling this function is usually a bug. pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 { if (native_os == .windows) { - const pathname_w = try windows.cStrToPrefixedFileW(null, pathname); - return realpathW(pathname_w.span(), out_buffer); + var pathname_w = try windows.cStrToPrefixedFileW(null, pathname); + + const wide_slice = try realpathW2(pathname_w.span(), &pathname_w.data); + + const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); + return out_buffer[0..end_index]; } else if (native_os == .wasi and !builtin.link_libc) { return realpath(mem.sliceTo(pathname, 0), out_buffer); } @@ -5737,34 +5743,24 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[max_path_bytes]u8) RealP return mem.sliceTo(result_path, 0); } +/// Deprecated: use `realpathW2`. +/// /// Same as `realpath` except `pathname` is WTF16LE-encoded. /// -/// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// /// Calling this function is usually a bug. pub fn realpathW(pathname: []const u16, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 { - const w = windows; - - const dir = fs.cwd().fd; - const access_mask = w.GENERIC_READ | w.SYNCHRONIZE; - const share_access = w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE; - const creation = w.FILE_OPEN; - const h_file = blk: { - const res = w.OpenFile(pathname, .{ - .dir = dir, - .access_mask = access_mask, - .share_access = share_access, - .creation = creation, - .filter = .any, - }) catch |err| switch (err) { - error.WouldBlock => unreachable, - else => |e| return e, - }; - break :blk res; - }; - defer w.CloseHandle(h_file); + return fs.cwd().realpathW(pathname, out_buffer); +} - return std.os.getFdPath(h_file, out_buffer); +/// Same as `realpath` except `pathname` is WTF16LE-encoded. +/// +/// The result is encoded as WTF16LE. +/// +/// Calling this function is usually a bug. +pub fn realpathW2(pathname: []const u16, out_buffer: *[std.os.windows.PATH_MAX_WIDE]u16) RealPathError![]u16 { + return fs.cwd().realpathW2(pathname, out_buffer); } /// Spurious wakeups are possible and no precision of timing is guaranteed. @@ -7590,7 +7586,7 @@ pub const UnexpectedError = error{ pub fn unexpectedErrno(err: E) UnexpectedError { if (unexpected_error_tracing) { std.debug.print("unexpected errno: {d}\n", .{@intFromEnum(err)}); - std.debug.dumpCurrentStackTrace(null); + std.debug.dumpCurrentStackTrace(.{}); } return error.Unexpected; } diff --git a/lib/std/process.zig b/lib/std/process.zig index 10025a5ea5..989e640859 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -20,7 +20,7 @@ pub const changeCurDirZ = posix.chdirZ; pub const GetCwdError = posix.GetCwdError; /// The result is a slice of `out_buffer`, from index `0`. -/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. pub fn getCwd(out_buffer: []u8) ![]u8 { return posix.getcwd(out_buffer); @@ -29,7 +29,7 @@ pub fn getCwd(out_buffer: []u8) ![]u8 { pub const GetCwdAllocError = Allocator.Error || posix.GetCwdError; /// Caller must free the returned memory. -/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. pub fn getCwdAlloc(allocator: Allocator) ![]u8 { // The use of max_path_bytes here is just a heuristic: most paths will fit @@ -139,7 +139,7 @@ pub const EnvMap = struct { /// Same as `put` but the key and value become owned by the EnvMap rather /// than being copied. /// If `putMove` fails, the ownership of key and value does not transfer. - /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string. + /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. pub fn putMove(self: *EnvMap, key: []u8, value: []u8) !void { assert(unicode.wtf8ValidateSlice(key)); const get_or_put = try self.hash_map.getOrPut(key); @@ -152,7 +152,7 @@ pub const EnvMap = struct { } /// `key` and `value` are copied into the EnvMap. - /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string. + /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. pub fn put(self: *EnvMap, key: []const u8, value: []const u8) !void { assert(unicode.wtf8ValidateSlice(key)); const value_copy = try self.copy(value); @@ -171,7 +171,7 @@ pub const EnvMap = struct { /// Find the address of the value associated with a key. /// The returned pointer is invalidated if the map resizes. - /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string. + /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. pub fn getPtr(self: EnvMap, key: []const u8) ?*[]const u8 { assert(unicode.wtf8ValidateSlice(key)); return self.hash_map.getPtr(key); @@ -180,7 +180,7 @@ pub const EnvMap = struct { /// Return the map's copy of the value associated with /// a key. The returned string is invalidated if this /// key is removed from the map. - /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string. + /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. pub fn get(self: EnvMap, key: []const u8) ?[]const u8 { assert(unicode.wtf8ValidateSlice(key)); return self.hash_map.get(key); @@ -188,7 +188,7 @@ pub const EnvMap = struct { /// Removes the item from the map and frees its value. /// This invalidates the value returned by get() for this key. - /// On Windows `key` must be a valid [WTF-8](https://simonsapin.github.io/wtf-8/) string. + /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. pub fn remove(self: *EnvMap, key: []const u8) void { assert(unicode.wtf8ValidateSlice(key)); const kv = self.hash_map.fetchRemove(key) orelse return; @@ -387,14 +387,14 @@ pub const GetEnvVarOwnedError = error{ EnvironmentVariableNotFound, /// On Windows, environment variable keys provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ + /// https://wtf-8.codeberg.page/ InvalidWtf8, }; /// Caller must free returned memory. -/// On Windows, if `key` is not valid [WTF-8](https://simonsapin.github.io/wtf-8/), +/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), /// then `error.InvalidWtf8` is returned. -/// On Windows, the value is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, the value is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On other platforms, the value is an opaque sequence of bytes with no particular encoding. pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 { if (native_os == .windows) { @@ -469,11 +469,11 @@ pub const HasEnvVarError = error{ OutOfMemory, /// On Windows, environment variable keys provided by the user must be valid WTF-8. - /// https://simonsapin.github.io/wtf-8/ + /// https://wtf-8.codeberg.page/ InvalidWtf8, }; -/// On Windows, if `key` is not valid [WTF-8](https://simonsapin.github.io/wtf-8/), +/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), /// then `error.InvalidWtf8` is returned. pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool { if (native_os == .windows) { @@ -491,7 +491,7 @@ pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool { } } -/// On Windows, if `key` is not valid [WTF-8](https://simonsapin.github.io/wtf-8/), +/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), /// then `error.InvalidWtf8` is returned. pub fn hasNonEmptyEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool { if (native_os == .windows) { @@ -737,7 +737,7 @@ pub const ArgIteratorWindows = struct { /// Returns the next argument and advances the iterator. Returns `null` if at the end of the /// command-line string. The iterator owns the returned slice. - /// The result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). + /// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/). pub fn next(self: *ArgIteratorWindows) ?[:0]const u8 { return self.nextWithStrategy(next_strategy); } @@ -771,7 +771,7 @@ pub const ArgIteratorWindows = struct { // check to see if we've emitted two consecutive surrogate // codepoints that form a valid surrogate pair in order // to ensure that we're always emitting well-formed WTF-8 - // (https://simonsapin.github.io/wtf-8/#concatenating). + // (https://wtf-8.codeberg.page/#concatenating). // // If we do have a valid surrogate pair, we need to emit // the UTF-8 sequence for the codepoint that they encode @@ -1196,7 +1196,7 @@ pub const ArgIterator = struct { /// Get the next argument. Returns 'null' if we are at the end. /// Returned slice is pointing to the iterator's internal buffer. - /// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). + /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. pub fn next(self: *ArgIterator) ?([:0]const u8) { return self.inner.next(); @@ -1234,7 +1234,7 @@ pub fn argsWithAllocator(allocator: Allocator) ArgIterator.InitError!ArgIterator } /// Caller must call argsFree on result. -/// On Windows, the result is encoded as [WTF-8](https://simonsapin.github.io/wtf-8/). +/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. pub fn argsAlloc(allocator: Allocator) ![][:0]u8 { // TODO refactor to only make 1 allocation. diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 02d7fd8d54..50157d52d9 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -162,7 +162,7 @@ pub const SpawnError = error{ NoDevice, /// Windows-only. `cwd` or `argv` was provided and it was invalid WTF-8. - /// https://simonsapin.github.io/wtf-8/ + /// https://wtf-8.codeberg.page/ InvalidWtf8, /// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process. diff --git a/lib/std/start.zig b/lib/std/start.zig index 7030616d6d..60ddae43ac 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -635,8 +635,11 @@ pub inline fn callMain() u8 { else => {}, } std.log.err("{s}", .{@errorName(err)}); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); + switch (native_os) { + .freestanding, .other => {}, + else => if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace); + }, } return 1; }; diff --git a/lib/std/std.zig b/lib/std/std.zig index 4a7f9bd866..4e68d1d611 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -171,6 +171,22 @@ pub const Options = struct { http_enable_ssl_key_log_file: bool = @import("builtin").mode == .Debug, side_channels_mitigations: crypto.SideChannelsMitigations = crypto.default_side_channels_mitigations, + + /// Whether to allow capturing and writing stack traces. This affects the following functions: + /// * `debug.captureCurrentStackTrace` + /// * `debug.writeCurrentStackTrace` + /// * `debug.dumpCurrentStackTrace` + /// * `debug.writeStackTrace` + /// * `debug.dumpStackTrace` + /// + /// Stack traces can generally be collected and printed when debug info is stripped, but are + /// often less useful since they usually cannot be mapped to source locations and/or have bad + /// source locations. The stack tracing logic can also be quite large, which may be undesirable, + /// particularly in ReleaseSmall. + /// + /// If this is `false`, then captured stack traces will always be empty, and attempts to write + /// stack traces will just print an error to the relevant `Io.Writer` and return. + allow_stack_tracing: bool = !@import("builtin").strip_debug_info, }; // This forces the start.zig file to be imported, and the comptime logic inside that diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 1c6c639796..e1e78c3bec 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -1242,3 +1242,63 @@ pub const Reader = struct { return n; } }; + +/// A `std.Io.Reader` that gets its data from another `std.Io.Reader`, and always +/// writes to its own buffer (and returns 0) during `stream` and `readVec`. +pub const ReaderIndirect = struct { + in: *std.Io.Reader, + interface: std.Io.Reader, + + pub fn init(in: *std.Io.Reader, buffer: []u8) ReaderIndirect { + return .{ + .in = in, + .interface = .{ + .vtable = &.{ + .stream = stream, + .readVec = readVec, + }, + .buffer = buffer, + .seek = 0, + .end = 0, + }, + }; + } + + fn readVec(r: *std.Io.Reader, _: [][]u8) std.Io.Reader.Error!usize { + try streamInner(r); + return 0; + } + + fn stream(r: *std.Io.Reader, _: *std.Io.Writer, _: std.Io.Limit) std.Io.Reader.StreamError!usize { + try streamInner(r); + return 0; + } + + fn streamInner(r: *std.Io.Reader) std.Io.Reader.Error!void { + const r_indirect: *ReaderIndirect = @alignCast(@fieldParentPtr("interface", r)); + + // If there's no room remaining in the buffer at all, make room. + if (r.buffer.len == r.end) { + try r.rebase(r.buffer.len); + } + + var writer: std.Io.Writer = .{ + .buffer = r.buffer, + .end = r.end, + .vtable = &.{ + .drain = std.Io.Writer.unreachableDrain, + .rebase = std.Io.Writer.unreachableRebase, + }, + }; + defer r.end = writer.end; + + r_indirect.in.streamExact(&writer, r.buffer.len - r.end) catch |err| switch (err) { + // Only forward EndOfStream if no new bytes were written to the buffer + error.EndOfStream => |e| if (r.end == writer.end) { + return e; + }, + error.WriteFailed => unreachable, + else => |e| return e, + }; + } +}; diff --git a/lib/std/testing/FailingAllocator.zig b/lib/std/testing/FailingAllocator.zig index c1f9791e39..6476725a2f 100644 --- a/lib/std/testing/FailingAllocator.zig +++ b/lib/std/testing/FailingAllocator.zig @@ -64,12 +64,8 @@ fn alloc( const self: *FailingAllocator = @ptrCast(@alignCast(ctx)); if (self.alloc_index == self.fail_index) { if (!self.has_induced_failure) { - @memset(&self.stack_addresses, 0); - var stack_trace = std.builtin.StackTrace{ - .instruction_addresses = &self.stack_addresses, - .index = 0, - }; - std.debug.captureStackTrace(return_address, &stack_trace); + const st = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &self.stack_addresses); + @memset(self.stack_addresses[@min(st.index, self.stack_addresses.len)..], 0); self.has_induced_failure = true; } return null; diff --git a/lib/std/zig/ErrorBundle.zig b/lib/std/zig/ErrorBundle.zig index 356b9fe137..8ec3ae18b6 100644 --- a/lib/std/zig/ErrorBundle.zig +++ b/lib/std/zig/ErrorBundle.zig @@ -416,6 +416,18 @@ pub const Wip = struct { wip.root_list.appendAssumeCapacity(try addErrorMessage(wip, em)); } + pub fn addRootErrorMessageWithNotes( + wip: *Wip, + msg: ErrorMessage, + notes: []const ErrorMessage, + ) !void { + try wip.addRootErrorMessage(msg); + const notes_start = try wip.reserveNotes(@intCast(notes.len)); + for (notes_start.., notes) |i, note| { + wip.extra.items[i] = @intFromEnum(wip.addErrorMessageAssumeCapacity(note)); + } + } + pub fn addErrorMessage(wip: *Wip, em: ErrorMessage) Allocator.Error!MessageIndex { return @enumFromInt(try addExtra(wip, em)); } diff --git a/lib/std/zig/Server.zig b/lib/std/zig/Server.zig index c035cbdec2..06b9afae91 100644 --- a/lib/std/zig/Server.zig +++ b/lib/std/zig/Server.zig @@ -231,6 +231,28 @@ pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void { try s.out.flush(); } +pub fn allocErrorBundle(allocator: std.mem.Allocator, body: []const u8) !std.zig.ErrorBundle { + const eb_hdr = @as(*align(1) const OutMessage.ErrorBundle, @ptrCast(body)); + const extra_bytes = + body[@sizeOf(OutMessage.ErrorBundle)..][0 .. @sizeOf(u32) * eb_hdr.extra_len]; + const string_bytes = + body[@sizeOf(OutMessage.ErrorBundle) + extra_bytes.len ..][0..eb_hdr.string_bytes_len]; + const unaligned_extra: []align(1) const u32 = @ptrCast(extra_bytes); + + var error_bundle: std.zig.ErrorBundle = .{ + .string_bytes = &.{}, + .extra = &.{}, + }; + errdefer error_bundle.deinit(allocator); + + error_bundle.string_bytes = try allocator.dupe(u8, string_bytes); + const extra = try allocator.alloc(u32, unaligned_extra.len); + @memcpy(extra, unaligned_extra); + error_bundle.extra = extra; + + return error_bundle; +} + pub const TestMetadata = struct { names: []const u32, expected_panic_msgs: []const u32, diff --git a/lib/std/zig/llvm/Builder.zig b/lib/std/zig/llvm/Builder.zig index 4afb0b8734..3bad020b9a 100644 --- a/lib/std/zig/llvm/Builder.zig +++ b/lib/std/zig/llvm/Builder.zig @@ -56,8 +56,8 @@ metadata_map: std.AutoArrayHashMapUnmanaged(void, void), metadata_items: std.MultiArrayList(Metadata.Item), metadata_extra: std.ArrayListUnmanaged(u32), metadata_limbs: std.ArrayListUnmanaged(std.math.big.Limb), -metadata_forward_references: std.ArrayListUnmanaged(Metadata), -metadata_named: std.AutoArrayHashMapUnmanaged(MetadataString, struct { +metadata_forward_references: std.ArrayListUnmanaged(Metadata.Optional), +metadata_named: std.AutoArrayHashMapUnmanaged(String, struct { len: u32, index: Metadata.Item.ExtraIndex, }), @@ -265,19 +265,20 @@ pub const Type = enum(u32) { }; pub const Simple = enum(u5) { - void = 2, - half = 10, - bfloat = 23, - float = 3, - double = 4, - fp128 = 14, - x86_fp80 = 13, - ppc_fp128 = 15, - x86_amx = 24, - x86_mmx = 17, - label = 5, - token = 22, - metadata = 16, + const Code = ir.ModuleBlock.TypeBlock.Code; + void = @intFromEnum(Code.VOID), + half = @intFromEnum(Code.HALF), + bfloat = @intFromEnum(Code.BFLOAT), + float = @intFromEnum(Code.FLOAT), + double = @intFromEnum(Code.DOUBLE), + fp128 = @intFromEnum(Code.FP128), + x86_fp80 = @intFromEnum(Code.X86_FP80), + ppc_fp128 = @intFromEnum(Code.PPC_FP128), + x86_amx = @intFromEnum(Code.X86_AMX), + x86_mmx = @intFromEnum(Code.X86_MMX), + label = @intFromEnum(Code.LABEL), + token = @intFromEnum(Code.TOKEN), + metadata = @intFromEnum(Code.METADATA), }; pub const Function = struct { @@ -1325,8 +1326,8 @@ pub const Attribute = union(Kind) { .none => unreachable, } } - pub fn fmt(self: Index, builder: *const Builder, mode: FormatData.mode) std.fmt.Alt(FormatData, format) { - return .{ .data = .{ .attribute_index = self, .builder = builder, .mode = mode } }; + pub fn fmt(self: Index, builder: *const Builder, flags: FormatData.Flags) std.fmt.Alt(FormatData, format) { + return .{ .data = .{ .attribute_index = self, .builder = builder, .flags = flags } }; } fn toStorage(self: Index, builder: *const Builder) Storage { @@ -2295,7 +2296,7 @@ pub const Global = struct { externally_initialized: ExternallyInitialized = .default, type: Type, partition: String = .none, - dbg: Metadata = .none, + dbg: Metadata.Optional = .none, kind: union(enum) { alias: Alias.Index, variable: Variable.Index, @@ -2375,7 +2376,11 @@ pub const Global = struct { } pub fn setDebugMetadata(self: Index, dbg: Metadata, builder: *Builder) void { - self.ptr(builder).dbg = dbg; + self.ptr(builder).dbg = dbg.toOptional(); + } + + pub fn getDebugMetadata(self: Index, builder: *const Builder) Metadata.Optional { + return self.ptrConst(builder).dbg; } const FormatData = struct { @@ -2606,6 +2611,10 @@ pub const Variable = struct { pub fn setGlobalVariableExpression(self: Index, expression: Metadata, builder: *Builder) void { self.ptrConst(builder).global.setDebugMetadata(expression, builder); } + + pub fn getGlobalVariableExpression(self: Index, builder: *Builder) Metadata.Optional { + return self.ptrConst(builder).global.getDebugMetadata(builder); + } }; }; @@ -4107,6 +4116,10 @@ pub const Function = struct { pub fn setSubprogram(self: Index, subprogram: Metadata, builder: *Builder) void { self.ptrConst(builder).global.setDebugMetadata(subprogram, builder); } + + pub fn getSubprogram(self: Index, builder: *const Builder) Metadata.Optional { + return self.ptrConst(builder).global.getDebugMetadata(builder); + } }; pub const Block = struct { @@ -4869,13 +4882,20 @@ pub const Function = struct { then: Block.Index, @"else": Block.Index, weights: Weights, + pub const Weights = enum(u32) { - // We can do this as metadata indices 0 and 1 are reserved. - none = 0, - unpredictable = 1, - /// These values should be converted to `Metadata` to be used - /// in a `prof` annotation providing branch weights. + none = @bitCast(Metadata.Optional.none), + unpredictable, _, + + pub fn fromMetadata(metadata: Metadata) Weights { + assert(metadata.kind == .node); + return @enumFromInt(metadata.index); + } + + pub fn toMetadata(weights: Weights) Metadata { + return .{ .index = @intCast(@intFromEnum(weights)), .kind = .node }; + } }; }; @@ -5130,19 +5150,19 @@ pub const DebugLocation = union(enum) { pub const Location = struct { line: u32, column: u32, - scope: Builder.Metadata, - inlined_at: Builder.Metadata, + scope: Builder.Metadata.Optional, + inlined_at: Builder.Metadata.Optional, }; - pub fn toMetadata(self: DebugLocation, builder: *Builder) Allocator.Error!Metadata { + pub fn toMetadata(self: DebugLocation, builder: *Builder) Allocator.Error!Metadata.Optional { return switch (self) { .no_location => .none, - .location => |location| try builder.debugLocation( + .location => |location| (try builder.debugLocation( location.line, location.column, - location.scope, - location.inlined_at, - ), + location.scope.unwrap().?, + location.inlined_at.unwrap(), + )).toOptional(), }; } }; @@ -5280,20 +5300,19 @@ pub const WipFunction = struct { .cond = cond, .then = then, .@"else" = @"else", - .weights = switch (weights) { + .weights = weights: switch (weights) { .none => .none, .unpredictable => .unpredictable, - .then_likely, .else_likely => w: { + .then_likely, .else_likely => { const branch_weights_str = try self.builder.metadataString("branch_weights"); const unlikely_const = try self.builder.metadataConstant(try self.builder.intConst(.i32, 1)); const likely_const = try self.builder.metadataConstant(try self.builder.intConst(.i32, 2000)); - const weight_vals: [2]Metadata = switch (weights) { + const weight_vals: [3]Metadata = switch (weights) { .none, .unpredictable => unreachable, - .then_likely => .{ likely_const, unlikely_const }, - .else_likely => .{ unlikely_const, likely_const }, + .then_likely => .{ branch_weights_str.toMetadata(), likely_const, unlikely_const }, + .else_likely => .{ branch_weights_str.toMetadata(), unlikely_const, likely_const }, }; - const tuple = try self.builder.strTuple(branch_weights_str, &weight_vals); - break :w @enumFromInt(@intFromEnum(tuple)); + break :weights .fromMetadata(try self.builder.metadataTuple(&weight_vals)); }, }, }), @@ -6197,20 +6216,18 @@ pub const WipFunction = struct { return instruction.toValue(); } - pub fn debugValue(self: *WipFunction, value: Value) Allocator.Error!Metadata { + pub fn debugValue(self: *WipFunction, value: Value) Allocator.Error!Metadata.Optional { if (self.strip) return .none; - return switch (value.unwrap()) { - .instruction => |instr_index| blk: { + const metadata: Metadata = metadata: switch (value.unwrap()) { + .instruction => |instr_index| { const gop = try self.debug_values.getOrPut(self.builder.gpa, instr_index); - - const metadata: Metadata = @enumFromInt(Metadata.first_local_metadata + gop.index); if (!gop.found_existing) gop.key_ptr.* = instr_index; - - break :blk metadata; + break :metadata .{ .index = @intCast(gop.index), .kind = .local }; }, .constant => |constant| try self.builder.metadataConstant(constant), .metadata => |metadata| metadata, }; + return metadata.toOptional(); } pub fn finish(self: *WipFunction) Allocator.Error!void { @@ -7820,7 +7837,7 @@ pub const Value = enum(u32) { else if (@intFromEnum(self) < first_metadata) .{ .constant = @enumFromInt(@intFromEnum(self) - first_constant) } else - .{ .metadata = @enumFromInt(@intFromEnum(self) - first_metadata) }; + .{ .metadata = @bitCast(@intFromEnum(self) - first_metadata) }; } pub fn typeOfWip(self: Value, wip: *const WipFunction) Type { @@ -7873,50 +7890,110 @@ pub const Value = enum(u32) { } }; -pub const MetadataString = enum(u32) { - none = 0, - _, +pub const Metadata = packed struct(u32) { + index: u29, + kind: Kind, + unused: enum(u1) { unused = 0 } = .unused, - pub fn slice(self: MetadataString, builder: *const Builder) []const u8 { - const index = @intFromEnum(self); - const start = builder.metadata_string_indices.items[index]; - const end = builder.metadata_string_indices.items[index + 1]; - return builder.metadata_string_bytes.items[start..end]; - } + pub const Kind = enum(u2) { + string, + node, + forward, + local, + }; - const Adapter = struct { - builder: *const Builder, - pub fn hash(_: Adapter, key: []const u8) u32 { - return @truncate(std.hash.Wyhash.hash(0, key)); + pub const empty_tuple: Metadata = .{ .kind = .node, .index = 0 }; + + pub const Optional = packed struct(u32) { + index: u29, + kind: Metadata.Kind, + is_none: bool, + + pub const none: Metadata.Optional = .{ .index = 0, .kind = .string, .is_none = true }; + pub const empty_tuple: Metadata.Optional = Metadata.empty_tuple.toOptional(); + + pub fn wrap(metadata: ?Metadata) Metadata.Optional { + return (metadata orelse return .none).toOptional(); } - pub fn eql(ctx: Adapter, lhs_key: []const u8, _: void, rhs_index: usize) bool { - const rhs_metadata_string: MetadataString = @enumFromInt(rhs_index); - return std.mem.eql(u8, lhs_key, rhs_metadata_string.slice(ctx.builder)); + pub fn unwrap(metadata: Metadata.Optional) ?Metadata { + return if (metadata.is_none) null else .{ .index = metadata.index, .kind = metadata.kind }; + } + pub fn toValue(metadata: Metadata.Optional) Value { + return if (metadata.unwrap()) |m| m.toValue() else .none; + } + pub fn toString(metadata: Metadata.Optional) Metadata.String.Optional { + return if (metadata.unwrap()) |m| m.toString().toOptional() else .none; } }; - - const FormatData = struct { - metadata_string: MetadataString, - builder: *const Builder, - }; - fn format(data: FormatData, w: *Writer) Writer.Error!void { - try printEscapedString(data.metadata_string.slice(data.builder), .always_quote, w); + pub fn toOptional(metadata: Metadata) Metadata.Optional { + return .{ .index = metadata.index, .kind = metadata.kind, .is_none = false }; } - fn fmt(self: MetadataString, builder: *const Builder) std.fmt.Alt(FormatData, format) { - return .{ .data = .{ .metadata_string = self, .builder = builder } }; + pub fn toValue(metadata: Metadata) Value { + return @enumFromInt(Value.first_metadata + @as(u32, @bitCast(metadata))); } -}; -pub const Metadata = enum(u32) { - none = 0, - empty_tuple = 1, - _, + pub const String = enum(u32) { + _, + + pub const Optional = enum(u32) { + none = @bitCast(Metadata.Optional.none), + _, + + pub fn wrap(metadata: ?Metadata.String) Metadata.String.Optional { + return (metadata orelse return .none).toOptional(); + } + pub fn unwrap(metadata: Metadata.String.Optional) ?Metadata.String { + return switch (metadata) { + .none => null, + else => @enumFromInt(@intFromEnum(metadata)), + }; + } + pub fn toMetadata(metadata: Metadata.String.Optional) Metadata.Optional { + return if (metadata.unwrap()) |m| m.toMetadata().toOptional() else .none; + } + }; + pub fn toOptional(metadata: Metadata.String) Metadata.String.Optional { + return @enumFromInt(@intFromEnum(metadata)); + } + pub fn toMetadata(metadata: Metadata.String) Metadata { + return .{ .index = @intCast(@intFromEnum(metadata)), .kind = .string }; + } - const first_forward_reference = 1 << 29; - const first_local_metadata = 1 << 30; + pub fn slice(metadata: Metadata.String, builder: *const Builder) []const u8 { + const index = @intFromEnum(metadata); + const start = builder.metadata_string_indices.items[index]; + const end = builder.metadata_string_indices.items[index + 1]; + return builder.metadata_string_bytes.items[start..end]; + } + + const Adapter = struct { + builder: *const Builder, + pub fn hash(_: Adapter, key: []const u8) u32 { + return @truncate(std.hash.Wyhash.hash(0, key)); + } + pub fn eql(ctx: Adapter, lhs_key: []const u8, _: void, rhs_index: usize) bool { + const rhs_metadata: Metadata.String = @enumFromInt(rhs_index); + return std.mem.eql(u8, lhs_key, rhs_metadata.slice(ctx.builder)); + } + }; + + const FormatData = struct { + metadata: Metadata.String, + builder: *const Builder, + }; + fn format(data: FormatData, w: *Writer) Writer.Error!void { + try printEscapedString(data.metadata.slice(data.builder), .always_quote, w); + } + fn fmt(self: Metadata.String, builder: *const Builder) std.fmt.Alt(FormatData, format) { + return .{ .data = .{ .metadata = self, .builder = builder } }; + } + }; + pub fn toString(metadata: Metadata) Metadata.String { + assert(metadata.kind == .string); + return @enumFromInt(metadata.index); + } pub const Tag = enum(u6) { - none, file, compile_unit, @"compile_unit optimized", @@ -7947,8 +8024,6 @@ pub const Metadata = enum(u32) { enumerator_signed_negative, subrange, tuple, - str_tuple, - module_flag, expression, local_var, parameter, @@ -7957,9 +8032,8 @@ pub const Metadata = enum(u32) { global_var_expression, constant, - pub fn isInline(tag: Tag) bool { - return switch (tag) { - .none, + pub fn isInline(metadata_tag: Metadata.Tag) bool { + return switch (metadata_tag) { .expression, .constant, => true, @@ -7993,8 +8067,6 @@ pub const Metadata = enum(u32) { .enumerator_signed_negative, .subrange, .tuple, - .str_tuple, - .module_flag, .local_var, .parameter, .global_var, @@ -8005,20 +8077,31 @@ pub const Metadata = enum(u32) { } }; - pub fn isInline(self: Metadata, builder: *const Builder) bool { - return builder.metadata_items.items(.tag)[@intFromEnum(self)].isInline(); + pub fn tag(metadata: Metadata, builder: *const Builder) Tag { + assert(metadata.kind == .node); + return builder.metadata_items.items(.tag)[metadata.index]; } - pub fn unwrap(self: Metadata, builder: *const Builder) Metadata { - var metadata = self; - while (@intFromEnum(metadata) >= Metadata.first_forward_reference and - @intFromEnum(metadata) < Metadata.first_local_metadata) - { - const index = @intFromEnum(metadata) - Metadata.first_forward_reference; - metadata = builder.metadata_forward_references.items[index]; - assert(metadata != .none); + pub fn item(metadata: Metadata, builder: *const Builder) Item { + assert(metadata.kind == .node); + return builder.metadata_items.get(metadata.index); + } + + pub fn isInline(metadata: Metadata, builder: *const Builder) bool { + return metadata.tag(builder).isInline(); + } + + pub fn unwrap(metadata: Metadata, builder: *const Builder) Metadata { + switch (metadata.kind) { + .string, .node, .local => return metadata, + .forward => { + const referenced = builder.metadata_forward_references.items[metadata.index].unwrap().?; + switch (referenced.kind) { + .string, .node => return referenced, + .forward, .local => unreachable, + } + }, } - return metadata; } pub const Item = struct { @@ -8086,8 +8169,8 @@ pub const Metadata = enum(u32) { }; pub const File = struct { - filename: MetadataString, - directory: MetadataString, + filename: Metadata.String.Optional, + directory: Metadata.String.Optional, }; pub const CompileUnit = struct { @@ -8095,10 +8178,10 @@ pub const Metadata = enum(u32) { optimized: bool, }; - file: Metadata, - producer: MetadataString, - enums: Metadata, - globals: Metadata, + file: Metadata.Optional, + producer: Metadata.String.Optional, + enums: Metadata.Optional, + globals: Metadata.Optional, }; pub const Subprogram = struct { @@ -8142,19 +8225,34 @@ pub const Metadata = enum(u32) { } }; - file: Metadata, - name: MetadataString, - linkage_name: MetadataString, + file: Metadata.Optional, + name: Metadata.String.Optional, + linkage_name: Metadata.String.Optional, line: u32, scope_line: u32, - ty: Metadata, + ty: Metadata.Optional, di_flags: DIFlags, - compile_unit: Metadata, + compile_unit: Metadata.Optional, }; + pub fn getSubprogram(metadata: Metadata, builder: *const Builder) Subprogram { + const metadata_item = metadata.item(builder); + switch (metadata_item.tag) { + else => unreachable, + .subprogram, + .@"subprogram local", + .@"subprogram definition", + .@"subprogram local definition", + .@"subprogram optimized", + .@"subprogram optimized local", + .@"subprogram optimized definition", + .@"subprogram optimized local definition", + => return builder.metadataExtraData(Metadata.Subprogram, metadata_item.data), + } + } pub const LexicalBlock = struct { - scope: Metadata, - file: Metadata, + scope: Metadata.Optional, + file: Metadata.Optional, line: u32, column: u32, }; @@ -8163,11 +8261,11 @@ pub const Metadata = enum(u32) { line: u32, column: u32, scope: Metadata, - inlined_at: Metadata, + inlined_at: Metadata.Optional, }; pub const BasicType = struct { - name: MetadataString, + name: Metadata.String.Optional, size_in_bits_lo: u32, size_in_bits_hi: u32, @@ -8177,16 +8275,16 @@ pub const Metadata = enum(u32) { }; pub const CompositeType = struct { - name: MetadataString, - file: Metadata, - scope: Metadata, + name: Metadata.String.Optional, + file: Metadata.Optional, + scope: Metadata.Optional, line: u32, - underlying_type: Metadata, + underlying_type: Metadata.Optional, size_in_bits_lo: u32, size_in_bits_hi: u32, align_in_bits_lo: u32, align_in_bits_hi: u32, - fields_tuple: Metadata, + fields_tuple: Metadata.Optional, pub fn bitSize(self: CompositeType) u64 { return @as(u64, self.size_in_bits_hi) << 32 | self.size_in_bits_lo; @@ -8197,11 +8295,11 @@ pub const Metadata = enum(u32) { }; pub const DerivedType = struct { - name: MetadataString, - file: Metadata, - scope: Metadata, + name: Metadata.String.Optional, + file: Metadata.Optional, + scope: Metadata.Optional, line: u32, - underlying_type: Metadata, + underlying_type: Metadata.Optional, size_in_bits_lo: u32, size_in_bits_hi: u32, align_in_bits_lo: u32, @@ -8221,19 +8319,19 @@ pub const Metadata = enum(u32) { }; pub const SubroutineType = struct { - types_tuple: Metadata, + types_tuple: Metadata.Optional, }; pub const Enumerator = struct { - name: MetadataString, + name: Metadata.String.Optional, bit_width: u32, limbs_index: u32, limbs_len: u32, }; pub const Subrange = struct { - lower_bound: Metadata, - count: Metadata, + lower_bound: Metadata.Optional, + count: Metadata.Optional, }; pub const Expression = struct { @@ -8248,33 +8346,20 @@ pub const Metadata = enum(u32) { // elements: [elements_len]Metadata }; - pub const StrTuple = struct { - str: MetadataString, - elements_len: u32, - - // elements: [elements_len]Metadata - }; - - pub const ModuleFlag = struct { - behavior: Metadata, - name: MetadataString, - constant: Metadata, - }; - pub const LocalVar = struct { - name: MetadataString, - file: Metadata, - scope: Metadata, + name: Metadata.String.Optional, + file: Metadata.Optional, + scope: Metadata.Optional, line: u32, - ty: Metadata, + ty: Metadata.Optional, }; pub const Parameter = struct { - name: MetadataString, - file: Metadata, - scope: Metadata, + name: Metadata.String.Optional, + file: Metadata.Optional, + scope: Metadata.Optional, line: u32, - ty: Metadata, + ty: Metadata.Optional, arg_no: u32, }; @@ -8283,24 +8368,20 @@ pub const Metadata = enum(u32) { local: bool, }; - name: MetadataString, - linkage_name: MetadataString, - file: Metadata, - scope: Metadata, + name: Metadata.String.Optional, + linkage_name: Metadata.String.Optional, + file: Metadata.Optional, + scope: Metadata.Optional, line: u32, - ty: Metadata, + ty: Metadata.Optional, variable: Variable.Index, }; pub const GlobalVarExpression = struct { - variable: Metadata, - expression: Metadata, + variable: Metadata.Optional, + expression: Metadata.Optional, }; - pub fn toValue(self: Metadata) Value { - return @enumFromInt(Value.first_metadata + @intFromEnum(self)); - } - const Formatter = struct { builder: *Builder, need_comma: bool, @@ -8325,7 +8406,7 @@ pub const Metadata = enum(u32) { local_inline: Metadata, local_index: u32, - string: MetadataString, + string: Metadata.String, bool: bool, u32: u32, u64: u64, @@ -8356,10 +8437,10 @@ pub const Metadata = enum(u32) { defer data.formatter.need_comma = needed_comma; data.formatter.need_comma = false; - const item = builder.metadata_items.get(@intFromEnum(node)); - switch (item.tag) { + const node_item = node.item(builder); + switch (node_item.tag) { .expression => { - var extra = builder.metadataExtraDataTrail(Expression, item.data); + var extra = builder.metadataExtraDataTrail(Expression, node_item.data); const elements = extra.trail.next(extra.data.elements_len, u32, builder); try w.writeAll("!DIExpression("); for (elements) |element| try format(.{ @@ -8370,7 +8451,7 @@ pub const Metadata = enum(u32) { try w.writeByte(')'); }, .constant => try Constant.format(.{ - .constant = @enumFromInt(item.data), + .constant = @enumFromInt(node_item.data), .builder = builder, .flags = data.specialized orelse .{}, }, w), @@ -8378,17 +8459,17 @@ pub const Metadata = enum(u32) { } }, .index => |node| try w.print("!{d}", .{node}), - inline .local_value, .local_metadata => |node, tag| try Value.format(.{ + inline .local_value, .local_metadata => |node, node_tag| try Value.format(.{ .value = node.value, .function = node.function, .builder = builder, - .flags = switch (tag) { + .flags = switch (node_tag) { .local_value => data.specialized orelse .{}, .local_metadata => .{ .percent = true }, else => unreachable, }, }, w), - inline .local_inline, .local_index => |node, tag| { + inline .local_inline, .local_index => |node, node_tag| { if (data.specialized) |flags| { if (flags.onlyPercent()) { try w.print("{f} ", .{Type.metadata.fmt(builder, .percent)}); @@ -8396,39 +8477,43 @@ pub const Metadata = enum(u32) { } try format(.{ .formatter = data.formatter, - .node = @unionInit(FormatData.Node, @tagName(tag)["local_".len..], node), + .node = @unionInit(FormatData.Node, @tagName(node_tag)["local_".len..], node), .specialized = .{ .percent = true }, }, w); }, - .string => |node| try w.print("{s}{f}", .{ - @as([]const u8, if (is_specialized) "!" else ""), node.fmt(builder), - }), + .string => |s| { + if (is_specialized) try w.writeByte('!'); + try w.print("{f}", .{s.fmt(builder)}); + }, inline .bool, .u32, .u64 => |node| try w.print("{}", .{node}), inline .di_flags, .sp_flags => |node| try w.print("{f}", .{node}), .raw => |node| try w.writeAll(node), } } inline fn fmt(formatter: *Formatter, prefix: []const u8, node: anytype, special: ?FormatFlags) switch (@TypeOf(node)) { - Metadata => Allocator.Error, + Metadata, Metadata.Optional, ?Metadata => Allocator.Error, else => error{}, }!std.fmt.Alt(FormatData, format) { const Node = @TypeOf(node); - const MaybeNode = switch (@typeInfo(Node)) { - .optional => Node, - .null => ?noreturn, - else => ?Node, + const MaybeNode = switch (Node) { + Metadata.Optional => ?Metadata, + Metadata.String.Optional => ?Metadata.String, + else => switch (@typeInfo(Node)) { + .optional => Node, + .null => ?noreturn, + else => ?Node, + }, }; const Some = @typeInfo(MaybeNode).optional.child; return .{ .data = .{ .formatter = formatter, .prefix = prefix, - .node = if (@as(MaybeNode, node)) |some| switch (@typeInfo(Some)) { + .node = if (@as(MaybeNode, switch (Node) { + Metadata.Optional, Metadata.String.Optional => node.unwrap(), + else => node, + })) |some| switch (@typeInfo(Some)) { .@"enum" => |enum_info| switch (Some) { - Metadata => switch (some) { - .none => .none, - else => try formatter.refUnwrapped(some.unwrap(formatter.builder)), - }, - MetadataString => .{ .string = some }, + Metadata.String => .{ .string = some }, else => if (enum_info.is_exhaustive) .{ .raw = @tagName(some) } else @@ -8438,15 +8523,23 @@ pub const Metadata = enum(u32) { .bool => .{ .bool = some }, .@"struct" => switch (Some) { DIFlags => .{ .di_flags = some }, + Metadata => switch (some.kind) { + .string => .{ .string = some.toString() }, + .node, .forward => try formatter.refUnwrapped(some.unwrap(formatter.builder)), + .local => unreachable, + }, Subprogram.DISPFlags => .{ .sp_flags = some }, else => @compileError("unknown type to format: " ++ @typeName(Node)), }, .int, .comptime_int => .{ .u64 = some }, .pointer => .{ .raw = some }, else => @compileError("unknown type to format: " ++ @typeName(Node)), - } else switch (@typeInfo(Node)) { - .optional, .null => .none, - else => unreachable, + } else switch (Node) { + Metadata.Optional, Metadata.String.Optional => .none, + else => switch (@typeInfo(Node)) { + .optional, .null => .none, + else => unreachable, + }, }, .specialized = special, } }; @@ -8460,24 +8553,26 @@ pub const Metadata = enum(u32) { return .{ .data = .{ .formatter = formatter, .prefix = prefix, - .node = switch (value.unwrap()) { + .node = node: switch (value.unwrap()) { .instruction, .constant => .{ .local_value = .{ .value = value, .function = function, } }, - .metadata => |metadata| if (value == .none) .none else node: { + .metadata => |metadata| if (value == .none) .none else { const unwrapped = metadata.unwrap(formatter.builder); - break :node if (@intFromEnum(unwrapped) >= first_local_metadata) - .{ .local_metadata = .{ + break :node switch (unwrapped.kind) { + .string, .node => switch (try formatter.refUnwrapped(unwrapped)) { + .@"inline" => |node| .{ .local_inline = node }, + .index => |node| .{ .local_index = node }, + else => unreachable, + }, + .forward => unreachable, + .local => .{ .local_metadata = .{ .value = function.ptrConst(formatter.builder).debug_values[ - @intFromEnum(unwrapped) - first_local_metadata + unwrapped.index ].toValue(), .function = function, - } } - else switch (try formatter.refUnwrapped(unwrapped)) { - .@"inline" => |node| .{ .local_inline = node }, - .index => |node| .{ .local_index = node }, - else => unreachable, + } }, }; }, }, @@ -8485,16 +8580,12 @@ pub const Metadata = enum(u32) { } }; } fn refUnwrapped(formatter: *Formatter, node: Metadata) Allocator.Error!FormatData.Node { - assert(node != .none); - assert(@intFromEnum(node) < first_forward_reference); const builder = formatter.builder; const unwrapped_metadata = node.unwrap(builder); - const tag = formatter.builder.metadata_items.items(.tag)[@intFromEnum(unwrapped_metadata)]; - switch (tag) { - .none => unreachable, + switch (unwrapped_metadata.tag(builder)) { .expression, .constant => return .{ .@"inline" = unwrapped_metadata }, - else => { - assert(!tag.isInline()); + else => |metadata_tag| { + assert(!metadata_tag.isInline()); const gop = try formatter.map.getOrPut(builder.gpa, .{ .metadata = unwrapped_metadata }); return .{ .index = @intCast(gop.index) }; }, @@ -8669,11 +8760,9 @@ pub fn init(options: Options) Allocator.Error!Builder { assert(try self.intConst(.i32, 1) == .@"1"); assert(try self.noneConst(.token) == .none); - assert(try self.metadataNone() == .none); - assert(try self.metadataTuple(&.{}) == .empty_tuple); + assert(try self.metadataTuple(&.{}) == Metadata.empty_tuple); try self.metadata_string_indices.append(self.gpa, 0); - assert(try self.metadataString("") == .none); return self; } @@ -9232,8 +9321,8 @@ pub fn halfConst(self: *Builder, val: f16) Allocator.Error!Constant { return self.halfConstAssumeCapacity(val); } -pub fn halfValue(self: *Builder, ty: Type, value: f16) Allocator.Error!Value { - return (try self.halfConst(ty, value)).toValue(); +pub fn halfValue(self: *Builder, value: f16) Allocator.Error!Value { + return (try self.halfConst(value)).toValue(); } pub fn bfloatConst(self: *Builder, val: f32) Allocator.Error!Constant { @@ -9241,8 +9330,8 @@ pub fn bfloatConst(self: *Builder, val: f32) Allocator.Error!Constant { return self.bfloatConstAssumeCapacity(val); } -pub fn bfloatValue(self: *Builder, ty: Type, value: f32) Allocator.Error!Value { - return (try self.bfloatConst(ty, value)).toValue(); +pub fn bfloatValue(self: *Builder, value: f32) Allocator.Error!Value { + return (try self.bfloatConst(value)).toValue(); } pub fn floatConst(self: *Builder, val: f32) Allocator.Error!Constant { @@ -9250,8 +9339,8 @@ pub fn floatConst(self: *Builder, val: f32) Allocator.Error!Constant { return self.floatConstAssumeCapacity(val); } -pub fn floatValue(self: *Builder, ty: Type, value: f32) Allocator.Error!Value { - return (try self.floatConst(ty, value)).toValue(); +pub fn floatValue(self: *Builder, value: f32) Allocator.Error!Value { + return (try self.floatConst(value)).toValue(); } pub fn doubleConst(self: *Builder, val: f64) Allocator.Error!Constant { @@ -9259,8 +9348,8 @@ pub fn doubleConst(self: *Builder, val: f64) Allocator.Error!Constant { return self.doubleConstAssumeCapacity(val); } -pub fn doubleValue(self: *Builder, ty: Type, value: f64) Allocator.Error!Value { - return (try self.doubleConst(ty, value)).toValue(); +pub fn doubleValue(self: *Builder, value: f64) Allocator.Error!Value { + return (try self.doubleConst(value)).toValue(); } pub fn fp128Const(self: *Builder, val: f128) Allocator.Error!Constant { @@ -9268,8 +9357,8 @@ pub fn fp128Const(self: *Builder, val: f128) Allocator.Error!Constant { return self.fp128ConstAssumeCapacity(val); } -pub fn fp128Value(self: *Builder, ty: Type, value: f128) Allocator.Error!Value { - return (try self.fp128Const(ty, value)).toValue(); +pub fn fp128Value(self: *Builder, value: f128) Allocator.Error!Value { + return (try self.fp128Const(value)).toValue(); } pub fn x86_fp80Const(self: *Builder, val: f80) Allocator.Error!Constant { @@ -9277,8 +9366,8 @@ pub fn x86_fp80Const(self: *Builder, val: f80) Allocator.Error!Constant { return self.x86_fp80ConstAssumeCapacity(val); } -pub fn x86_fp80Value(self: *Builder, ty: Type, value: f80) Allocator.Error!Value { - return (try self.x86_fp80Const(ty, value)).toValue(); +pub fn x86_fp80Value(self: *Builder, value: f80) Allocator.Error!Value { + return (try self.x86_fp80Const(value)).toValue(); } pub fn ppc_fp128Const(self: *Builder, val: [2]f64) Allocator.Error!Constant { @@ -9286,8 +9375,8 @@ pub fn ppc_fp128Const(self: *Builder, val: [2]f64) Allocator.Error!Constant { return self.ppc_fp128ConstAssumeCapacity(val); } -pub fn ppc_fp128Value(self: *Builder, ty: Type, value: [2]f64) Allocator.Error!Value { - return (try self.ppc_fp128Const(ty, value)).toValue(); +pub fn ppc_fp128Value(self: *Builder, value: [2]f64) Allocator.Error!Value { + return (try self.ppc_fp128Const(value)).toValue(); } pub fn nullConst(self: *Builder, ty: Type) Allocator.Error!Constant { @@ -9870,7 +9959,7 @@ pub fn print(self: *Builder, w: *Writer) (Writer.Error || Allocator.Error)!void .none => {}, .unpredictable => try w.writeAll("!unpredictable !{}"), _ => try w.print("{f}", .{ - try metadata_formatter.fmt("!prof ", @as(Metadata, @enumFromInt(@intFromEnum(extra.weights))), null), + try metadata_formatter.fmt("!prof ", extra.weights.toMetadata(), null), }), } }, @@ -10153,7 +10242,7 @@ pub fn print(self: *Builder, w: *Writer) (Writer.Error || Allocator.Error)!void .none => {}, .unpredictable => try w.writeAll("!unpredictable !{}"), _ => try w.print("{f}", .{ - try metadata_formatter.fmt("!prof ", @as(Metadata, @enumFromInt(@intFromEnum(extra.data.weights))), null), + try metadata_formatter.fmt("!prof ", extra.data.weights.toMetadata(), null), }), } }, @@ -10193,7 +10282,7 @@ pub fn print(self: *Builder, w: *Writer) (Writer.Error || Allocator.Error)!void const elements: []const Metadata = @ptrCast(self.metadata_extra.items[data.index..][0..data.len]); try w.writeByte('!'); - try printEscapedString(name.slice(self), .quote_unless_valid_identifier, w); + try printEscapedString(name.slice(self).?, .quote_unless_valid_identifier, w); try w.writeAll(" = !{"); metadata_formatter.need_comma = false; defer metadata_formatter.need_comma = undefined; @@ -10223,11 +10312,11 @@ pub fn print(self: *Builder, w: *Writer) (Writer.Error || Allocator.Error)!void }, w); continue; }, - .metadata => |metadata| self.metadata_items.get(@intFromEnum(metadata)), + .metadata => |metadata| metadata.item(self), }; switch (metadata_item.tag) { - .none, .expression, .constant => unreachable, + .expression, .constant => unreachable, .file => { const extra = self.metadataExtraData(Metadata.File, metadata_item.data); try metadata_formatter.specialized(.@"!", .DIFile, .{ @@ -10330,10 +10419,7 @@ pub fn print(self: *Builder, w: *Writer) (Writer.Error || Allocator.Error)!void const extra = self.metadataExtraData(Metadata.BasicType, metadata_item.data); try metadata_formatter.specialized(.@"!", .DIBasicType, .{ .tag = null, - .name = switch (extra.name) { - .none => null, - else => extra.name, - }, + .name = extra.name, .size = extra.bitSize(), .@"align" = null, .encoding = @as(enum { @@ -10371,10 +10457,7 @@ pub fn print(self: *Builder, w: *Writer) (Writer.Error || Allocator.Error)!void .composite_array_type, .composite_vector_type => .DW_TAG_array_type, else => unreachable, }), - .name = switch (extra.name) { - .none => null, - else => extra.name, - }, + .name = extra.name, .scope = extra.scope, .file = null, .line = null, @@ -10409,10 +10492,7 @@ pub fn print(self: *Builder, w: *Writer) (Writer.Error || Allocator.Error)!void .derived_member_type => .DW_TAG_member, else => unreachable, }), - .name = switch (extra.name) { - .none => null, - else => extra.name, - }, + .name = extra.name, .scope = extra.scope, .file = null, .line = null, @@ -10505,25 +10585,6 @@ pub fn print(self: *Builder, w: *Writer) (Writer.Error || Allocator.Error)!void }); try w.writeAll("}\n"); }, - .str_tuple => { - var extra = self.metadataExtraDataTrail(Metadata.StrTuple, metadata_item.data); - const elements = extra.trail.next(extra.data.elements_len, Metadata, self); - try w.print("!{{{[str]f}", .{ - .str = try metadata_formatter.fmt("", extra.data.str, .{ .percent = true }), - }); - for (elements) |element| try w.print("{[element]f}", .{ - .element = try metadata_formatter.fmt("", element, .{ .percent = true }), - }); - try w.writeAll("}\n"); - }, - .module_flag => { - const extra = self.metadataExtraData(Metadata.ModuleFlag, metadata_item.data); - try w.print("!{{{[behavior]f}{[name]f}{[constant]f}}}\n", .{ - .behavior = try metadata_formatter.fmt("", extra.behavior, .{ .percent = true }), - .name = try metadata_formatter.fmt("", extra.name, .{ .percent = true }), - .constant = try metadata_formatter.fmt("", extra.constant, .{ .percent = true }), - }); - }, .local_var => { const extra = self.metadataExtraData(Metadata.LocalVar, metadata_item.data); try metadata_formatter.specialized(.@"!", .DILocalVariable, .{ @@ -11914,8 +11975,8 @@ fn addMetadataExtraAssumeCapacity(self: *Builder, extra: anytype) Metadata.Item. const value = @field(extra, field.name); self.metadata_extra.appendAssumeCapacity(switch (field.type) { u32 => value, - MetadataString, Metadata, Variable.Index, Value => @intFromEnum(value), - Metadata.DIFlags => @bitCast(value), + Metadata.String, Metadata.String.Optional, Variable.Index, Value => @intFromEnum(value), + Metadata, Metadata.Optional, Metadata.DIFlags => @bitCast(value), else => @compileError("bad field type: " ++ @typeName(field.type)), }); } @@ -11953,8 +12014,8 @@ fn metadataExtraDataTrail( inline for (fields, self.metadata_extra.items[index..][0..fields.len]) |field, value| @field(result, field.name) = switch (field.type) { u32 => value, - MetadataString, Metadata, Variable.Index, Value => @enumFromInt(value), - Metadata.DIFlags => @bitCast(value), + Metadata.String, Metadata.String.Optional, Variable.Index, Value => @enumFromInt(value), + Metadata, Metadata.Optional, Metadata.DIFlags => @bitCast(value), else => @compileError("bad field type: " ++ @typeName(field.type)), }; return .{ @@ -11967,48 +12028,65 @@ fn metadataExtraData(self: *const Builder, comptime T: type, index: Metadata.Ite return self.metadataExtraDataTrail(T, index).data; } -pub fn metadataString(self: *Builder, bytes: []const u8) Allocator.Error!MetadataString { +pub fn metadataString(self: *Builder, bytes: []const u8) Allocator.Error!Metadata.String { + assert(bytes.len > 0); try self.metadata_string_bytes.ensureUnusedCapacity(self.gpa, bytes.len); try self.metadata_string_indices.ensureUnusedCapacity(self.gpa, 1); try self.metadata_string_map.ensureUnusedCapacity(self.gpa, 1); const gop = self.metadata_string_map.getOrPutAssumeCapacityAdapted( bytes, - MetadataString.Adapter{ .builder = self }, + Metadata.String.Adapter{ .builder = self }, ); if (!gop.found_existing) { self.metadata_string_bytes.appendSliceAssumeCapacity(bytes); - self.metadata_string_indices.appendAssumeCapacity(@intCast(self.metadata_string_bytes.items.len)); + self.metadata_string_indices.appendAssumeCapacity( + @intCast(self.metadata_string_bytes.items.len), + ); } return @enumFromInt(gop.index); } -pub fn metadataStringFromStrtabString(self: *Builder, str: StrtabString) Allocator.Error!MetadataString { - if (str == .none or str == .empty) return MetadataString.none; +pub fn metadataStringFromStrtabString( + self: *Builder, + str: StrtabString, +) Allocator.Error!Metadata.String { return try self.metadataString(str.slice(self).?); } -pub fn metadataStringFmt(self: *Builder, comptime fmt_str: []const u8, fmt_args: anytype) Allocator.Error!MetadataString { +pub fn metadataStringFmt( + self: *Builder, + comptime fmt_str: []const u8, + fmt_args: anytype, +) Allocator.Error!Metadata.String { try self.metadata_string_map.ensureUnusedCapacity(self.gpa, 1); - try self.metadata_string_bytes.ensureUnusedCapacity(self.gpa, @intCast(std.fmt.count(fmt_str, fmt_args))); + try self.metadata_string_bytes.ensureUnusedCapacity( + self.gpa, + @intCast(std.fmt.count(fmt_str, fmt_args)), + ); try self.metadata_string_indices.ensureUnusedCapacity(self.gpa, 1); return self.metadataStringFmtAssumeCapacity(fmt_str, fmt_args); } -pub fn metadataStringFmtAssumeCapacity(self: *Builder, comptime fmt_str: []const u8, fmt_args: anytype) MetadataString { +pub fn metadataStringFmtAssumeCapacity( + self: *Builder, + comptime fmt_str: []const u8, + fmt_args: anytype, +) Metadata.String { self.metadata_string_bytes.printAssumeCapacity(fmt_str, fmt_args); return self.trailingMetadataStringAssumeCapacity(); } -pub fn trailingMetadataString(self: *Builder) Allocator.Error!MetadataString { +pub fn trailingMetadataString(self: *Builder) Allocator.Error!Metadata.String { try self.metadata_string_indices.ensureUnusedCapacity(self.gpa, 1); try self.metadata_string_map.ensureUnusedCapacity(self.gpa, 1); return self.trailingMetadataStringAssumeCapacity(); } -pub fn trailingMetadataStringAssumeCapacity(self: *Builder) MetadataString { +pub fn trailingMetadataStringAssumeCapacity(self: *Builder) Metadata.String { const start = self.metadata_string_indices.getLast(); const bytes: []const u8 = self.metadata_string_bytes.items[start..]; + assert(bytes.len > 0); const gop = self.metadata_string_map.getOrPutAssumeCapacityAdapted(bytes, String.Adapter{ .builder = self }); if (gop.found_existing) { self.metadata_string_bytes.shrinkRetainingCapacity(start); @@ -12018,21 +12096,16 @@ pub fn trailingMetadataStringAssumeCapacity(self: *Builder) MetadataString { return @enumFromInt(gop.index); } -pub fn metadataNamed(self: *Builder, name: MetadataString, operands: []const Metadata) Allocator.Error!void { +pub fn addNamedMetadata(self: *Builder, name: String, operands: []const Metadata) Allocator.Error!void { try self.metadata_extra.ensureUnusedCapacity(self.gpa, operands.len); try self.metadata_named.ensureUnusedCapacity(self.gpa, 1); - self.metadataNamedAssumeCapacity(name, operands); -} - -fn metadataNone(self: *Builder) Allocator.Error!Metadata { - try self.ensureUnusedMetadataCapacity(1, NoExtra, 0); - return self.metadataNoneAssumeCapacity(); + self.addNamedMetadataAssumeCapacity(name, operands); } pub fn debugFile( self: *Builder, - filename: MetadataString, - directory: MetadataString, + filename: ?Metadata.String, + directory: ?Metadata.String, ) Allocator.Error!Metadata { try self.ensureUnusedMetadataCapacity(1, Metadata.File, 0); return self.debugFileAssumeCapacity(filename, directory); @@ -12040,10 +12113,10 @@ pub fn debugFile( pub fn debugCompileUnit( self: *Builder, - file: Metadata, - producer: MetadataString, - enums: Metadata, - globals: Metadata, + file: ?Metadata, + producer: ?Metadata.String, + enums: ?Metadata, + globals: ?Metadata, options: Metadata.CompileUnit.Options, ) Allocator.Error!Metadata { try self.ensureUnusedMetadataCapacity(1, Metadata.CompileUnit, 0); @@ -12052,14 +12125,14 @@ pub fn debugCompileUnit( pub fn debugSubprogram( self: *Builder, - file: Metadata, - name: MetadataString, - linkage_name: MetadataString, + file: ?Metadata, + name: ?Metadata.String, + linkage_name: ?Metadata.String, line: u32, scope_line: u32, - ty: Metadata, + ty: ?Metadata, options: Metadata.Subprogram.Options, - compile_unit: Metadata, + compile_unit: ?Metadata, ) Allocator.Error!Metadata { try self.ensureUnusedMetadataCapacity(1, Metadata.Subprogram, 0); return self.debugSubprogramAssumeCapacity( @@ -12074,32 +12147,60 @@ pub fn debugSubprogram( ); } -pub fn debugLexicalBlock(self: *Builder, scope: Metadata, file: Metadata, line: u32, column: u32) Allocator.Error!Metadata { +pub fn debugLexicalBlock( + self: *Builder, + scope: ?Metadata, + file: ?Metadata, + line: u32, + column: u32, +) Allocator.Error!Metadata { try self.ensureUnusedMetadataCapacity(1, Metadata.LexicalBlock, 0); return self.debugLexicalBlockAssumeCapacity(scope, file, line, column); } -pub fn debugLocation(self: *Builder, line: u32, column: u32, scope: Metadata, inlined_at: Metadata) Allocator.Error!Metadata { +pub fn debugLocation( + self: *Builder, + line: u32, + column: u32, + scope: Metadata, + inlined_at: ?Metadata, +) Allocator.Error!Metadata { try self.ensureUnusedMetadataCapacity(1, Metadata.Location, 0); return self.debugLocationAssumeCapacity(line, column, scope, inlined_at); } -pub fn debugBoolType(self: *Builder, name: MetadataString, size_in_bits: u64) Allocator.Error!Metadata { +pub fn debugBoolType( + self: *Builder, + name: ?Metadata.String, + size_in_bits: u64, +) Allocator.Error!Metadata { try self.ensureUnusedMetadataCapacity(1, Metadata.BasicType, 0); return self.debugBoolTypeAssumeCapacity(name, size_in_bits); } -pub fn debugUnsignedType(self: *Builder, name: MetadataString, size_in_bits: u64) Allocator.Error!Metadata { +pub fn debugUnsignedType( + self: *Builder, + name: ?Metadata.String, + size_in_bits: u64, +) Allocator.Error!Metadata { try self.ensureUnusedMetadataCapacity(1, Metadata.BasicType, 0); return self.debugUnsignedTypeAssumeCapacity(name, size_in_bits); } -pub fn debugSignedType(self: *Builder, name: MetadataString, size_in_bits: u64) Allocator.Error!Metadata { +pub fn debugSignedType( + self: *Builder, + name: ?Metadata.String, + size_in_bits: u64, +) Allocator.Error!Metadata { try self.ensureUnusedMetadataCapacity(1, Metadata.BasicType, 0); return self.debugSignedTypeAssumeCapacity(name, size_in_bits); } -pub fn debugFloatType(self: *Builder, name: MetadataString, size_in_bits: u64) Allocator.Error!Metadata { +pub fn debugFloatType( + self: *Builder, + name: ?Metadata.String, + size_in_bits: u64, +) Allocator.Error!Metadata { try self.ensureUnusedMetadataCapacity(1, Metadata.BasicType, 0); return self.debugFloatTypeAssumeCapacity(name, size_in_bits); } @@ -12111,14 +12212,14 @@ pub fn debugForwardReference(self: *Builder) Allocator.Error!Metadata { pub fn debugStructType( self: *Builder, - name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - underlying_type: Metadata, + underlying_type: ?Metadata, size_in_bits: u64, align_in_bits: u64, - fields_tuple: Metadata, + fields_tuple: ?Metadata, ) Allocator.Error!Metadata { try self.ensureUnusedMetadataCapacity(1, Metadata.CompositeType, 0); return self.debugStructTypeAssumeCapacity( @@ -12135,14 +12236,14 @@ pub fn debugStructType( pub fn debugUnionType( self: *Builder, - name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - underlying_type: Metadata, + underlying_type: ?Metadata, size_in_bits: u64, align_in_bits: u64, - fields_tuple: Metadata, + fields_tuple: ?Metadata, ) Allocator.Error!Metadata { try self.ensureUnusedMetadataCapacity(1, Metadata.CompositeType, 0); return self.debugUnionTypeAssumeCapacity( @@ -12159,14 +12260,14 @@ pub fn debugUnionType( pub fn debugEnumerationType( self: *Builder, - name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - underlying_type: Metadata, + underlying_type: ?Metadata, size_in_bits: u64, align_in_bits: u64, - fields_tuple: Metadata, + fields_tuple: ?Metadata, ) Allocator.Error!Metadata { try self.ensureUnusedMetadataCapacity(1, Metadata.CompositeType, 0); return self.debugEnumerationTypeAssumeCapacity( @@ -12183,14 +12284,14 @@ pub fn debugEnumerationType( pub fn debugArrayType( self: *Builder, - name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - underlying_type: Metadata, + underlying_type: ?Metadata, size_in_bits: u64, align_in_bits: u64, - fields_tuple: Metadata, + fields_tuple: ?Metadata, ) Allocator.Error!Metadata { try self.ensureUnusedMetadataCapacity(1, Metadata.CompositeType, 0); return self.debugArrayTypeAssumeCapacity( @@ -12207,14 +12308,14 @@ pub fn debugArrayType( pub fn debugVectorType( self: *Builder, - name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - underlying_type: Metadata, + underlying_type: ?Metadata, size_in_bits: u64, align_in_bits: u64, - fields_tuple: Metadata, + fields_tuple: ?Metadata, ) Allocator.Error!Metadata { try self.ensureUnusedMetadataCapacity(1, Metadata.CompositeType, 0); return self.debugVectorTypeAssumeCapacity( @@ -12231,11 +12332,11 @@ pub fn debugVectorType( pub fn debugPointerType( self: *Builder, - name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - underlying_type: Metadata, + underlying_type: ?Metadata, size_in_bits: u64, align_in_bits: u64, offset_in_bits: u64, @@ -12255,11 +12356,11 @@ pub fn debugPointerType( pub fn debugMemberType( self: *Builder, - name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - underlying_type: Metadata, + underlying_type: ?Metadata, size_in_bits: u64, align_in_bits: u64, offset_in_bits: u64, @@ -12277,17 +12378,14 @@ pub fn debugMemberType( ); } -pub fn debugSubroutineType( - self: *Builder, - types_tuple: Metadata, -) Allocator.Error!Metadata { +pub fn debugSubroutineType(self: *Builder, types_tuple: ?Metadata) Allocator.Error!Metadata { try self.ensureUnusedMetadataCapacity(1, Metadata.SubroutineType, 0); return self.debugSubroutineTypeAssumeCapacity(types_tuple); } pub fn debugEnumerator( self: *Builder, - name: MetadataString, + name: ?Metadata.String, unsigned: bool, bit_width: u32, value: std.math.big.int.Const, @@ -12300,55 +12398,37 @@ pub fn debugEnumerator( pub fn debugSubrange( self: *Builder, - lower_bound: Metadata, - count: Metadata, + lower_bound: ?Metadata, + count: ?Metadata, ) Allocator.Error!Metadata { try self.ensureUnusedMetadataCapacity(1, Metadata.Subrange, 0); return self.debugSubrangeAssumeCapacity(lower_bound, count); } -pub fn debugExpression( - self: *Builder, - elements: []const u32, -) Allocator.Error!Metadata { +pub fn debugExpression(self: *Builder, elements: []const u32) Allocator.Error!Metadata { try self.ensureUnusedMetadataCapacity(1, Metadata.Expression, elements.len); return self.debugExpressionAssumeCapacity(elements); } -pub fn metadataTuple( - self: *Builder, - elements: []const Metadata, -) Allocator.Error!Metadata { - try self.ensureUnusedMetadataCapacity(1, Metadata.Tuple, elements.len); - return self.metadataTupleAssumeCapacity(elements); -} - -pub fn strTuple( - self: *Builder, - str: MetadataString, - elements: []const Metadata, -) Allocator.Error!Metadata { - try self.ensureUnusedMetadataCapacity(1, Metadata.StrTuple, elements.len); - return self.strTupleAssumeCapacity(str, elements); +pub fn metadataTuple(self: *Builder, elements: []const Metadata) Allocator.Error!Metadata { + return self.metadataTupleOptionals(@ptrCast(elements)); } -pub fn metadataModuleFlag( +pub fn metadataTupleOptionals( self: *Builder, - behavior: Metadata, - name: MetadataString, - constant: Metadata, + elements: []const Metadata.Optional, ) Allocator.Error!Metadata { - try self.ensureUnusedMetadataCapacity(1, Metadata.ModuleFlag, 0); - return self.metadataModuleFlagAssumeCapacity(behavior, name, constant); + try self.ensureUnusedMetadataCapacity(1, Metadata.Tuple, elements.len); + return self.metadataTupleOptionalsAssumeCapacity(elements); } pub fn debugLocalVar( self: *Builder, - name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - ty: Metadata, + ty: ?Metadata, ) Allocator.Error!Metadata { try self.ensureUnusedMetadataCapacity(1, Metadata.LocalVar, 0); return self.debugLocalVarAssumeCapacity(name, file, scope, line, ty); @@ -12356,11 +12436,11 @@ pub fn debugLocalVar( pub fn debugParameter( self: *Builder, - name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - ty: Metadata, + ty: ?Metadata, arg_no: u32, ) Allocator.Error!Metadata { try self.ensureUnusedMetadataCapacity(1, Metadata.Parameter, 0); @@ -12369,12 +12449,12 @@ pub fn debugParameter( pub fn debugGlobalVar( self: *Builder, - name: MetadataString, - linkage_name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + linkage_name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - ty: Metadata, + ty: ?Metadata, variable: Variable.Index, options: Metadata.GlobalVar.Options, ) Allocator.Error!Metadata { @@ -12393,8 +12473,8 @@ pub fn debugGlobalVar( pub fn debugGlobalVarExpression( self: *Builder, - variable: Metadata, - expression: Metadata, + variable: ?Metadata, + expression: ?Metadata, ) Allocator.Error!Metadata { try self.ensureUnusedMetadataCapacity(1, Metadata.GlobalVarExpression, 0); return self.debugGlobalVarExpressionAssumeCapacity(variable, expression); @@ -12405,13 +12485,11 @@ pub fn metadataConstant(self: *Builder, value: Constant) Allocator.Error!Metadat return self.metadataConstantAssumeCapacity(value); } -pub fn debugForwardReferenceSetType(self: *Builder, fwd_ref: Metadata, ty: Metadata) void { - assert( - @intFromEnum(fwd_ref) >= Metadata.first_forward_reference and - @intFromEnum(fwd_ref) <= Metadata.first_local_metadata, - ); - const index = @intFromEnum(fwd_ref) - Metadata.first_forward_reference; - self.metadata_forward_references.items[index] = ty; +pub fn resolveDebugForwardReference(self: *Builder, fwd_ref: Metadata, value: Metadata) void { + assert(fwd_ref.kind == .forward); + const resolved = &self.metadata_forward_references.items[fwd_ref.index]; + assert(resolved.is_none); + resolved.* = value.toOptional(); } fn metadataSimpleAssumeCapacity(self: *Builder, tag: Metadata.Tag, value: anytype) Metadata { @@ -12450,41 +12528,20 @@ fn metadataSimpleAssumeCapacity(self: *Builder, tag: Metadata.Tag, value: anytyp .data = self.addMetadataExtraAssumeCapacity(value), }); } - return @enumFromInt(gop.index); + return .{ .index = @intCast(gop.index), .kind = .node }; } fn metadataDistinctAssumeCapacity(self: *Builder, tag: Metadata.Tag, value: anytype) Metadata { - const Key = struct { tag: Metadata.Tag, index: Metadata }; - const Adapter = struct { - pub fn hash(_: @This(), key: Key) u32 { - return @truncate(std.hash.Wyhash.hash( - std.hash.int(@intFromEnum(key.tag)), - std.mem.asBytes(&key.index), - )); - } - - pub fn eql(_: @This(), lhs_key: Key, _: void, rhs_index: usize) bool { - return @intFromEnum(lhs_key.index) == rhs_index; - } - }; - - const gop = self.metadata_map.getOrPutAssumeCapacityAdapted( - Key{ .tag = tag, .index = @enumFromInt(self.metadata_map.count()) }, - Adapter{}, - ); - - if (!gop.found_existing) { - gop.key_ptr.* = {}; - gop.value_ptr.* = {}; - self.metadata_items.appendAssumeCapacity(.{ - .tag = tag, - .data = self.addMetadataExtraAssumeCapacity(value), - }); - } - return @enumFromInt(gop.index); + const index = self.metadata_items.len; + _ = self.metadata_map.entries.addOneAssumeCapacity(); + self.metadata_items.appendAssumeCapacity(.{ + .tag = tag, + .data = self.addMetadataExtraAssumeCapacity(value), + }); + return .{ .index = @intCast(index), .kind = .node }; } -fn metadataNamedAssumeCapacity(self: *Builder, name: MetadataString, operands: []const Metadata) void { +fn addNamedMetadataAssumeCapacity(self: *Builder, name: String, operands: []const Metadata) void { assert(name != .none); const extra_index: u32 = @intCast(self.metadata_extra.items.len); self.metadata_extra.appendSliceAssumeCapacity(@ptrCast(operands)); @@ -12496,119 +12553,127 @@ fn metadataNamedAssumeCapacity(self: *Builder, name: MetadataString, operands: [ }; } -pub fn metadataNoneAssumeCapacity(self: *Builder) Metadata { - return self.metadataSimpleAssumeCapacity(.none, .{}); -} - fn debugFileAssumeCapacity( self: *Builder, - filename: MetadataString, - directory: MetadataString, + filename: ?Metadata.String, + directory: ?Metadata.String, ) Metadata { assert(!self.strip); return self.metadataSimpleAssumeCapacity(.file, Metadata.File{ - .filename = filename, - .directory = directory, + .filename = .wrap(filename), + .directory = .wrap(directory), }); } pub fn debugCompileUnitAssumeCapacity( self: *Builder, - file: Metadata, - producer: MetadataString, - enums: Metadata, - globals: Metadata, + file: ?Metadata, + producer: ?Metadata.String, + enums: ?Metadata, + globals: ?Metadata, options: Metadata.CompileUnit.Options, ) Metadata { assert(!self.strip); return self.metadataDistinctAssumeCapacity( if (options.optimized) .@"compile_unit optimized" else .compile_unit, Metadata.CompileUnit{ - .file = file, - .producer = producer, - .enums = enums, - .globals = globals, + .file = .wrap(file), + .producer = .wrap(producer), + .enums = .wrap(enums), + .globals = .wrap(globals), }, ); } fn debugSubprogramAssumeCapacity( self: *Builder, - file: Metadata, - name: MetadataString, - linkage_name: MetadataString, + file: ?Metadata, + name: ?Metadata.String, + linkage_name: ?Metadata.String, line: u32, scope_line: u32, - ty: Metadata, + ty: ?Metadata, options: Metadata.Subprogram.Options, - compile_unit: Metadata, + compile_unit: ?Metadata, ) Metadata { assert(!self.strip); const tag: Metadata.Tag = @enumFromInt(@intFromEnum(Metadata.Tag.subprogram) + @as(u3, @truncate(@as(u32, @bitCast(options.sp_flags)) >> 2))); return self.metadataDistinctAssumeCapacity(tag, Metadata.Subprogram{ - .file = file, - .name = name, - .linkage_name = linkage_name, + .file = .wrap(file), + .name = .wrap(name), + .linkage_name = .wrap(linkage_name), .line = line, .scope_line = scope_line, - .ty = ty, + .ty = .wrap(ty), .di_flags = options.di_flags, - .compile_unit = compile_unit, + .compile_unit = .wrap(compile_unit), }); } -fn debugLexicalBlockAssumeCapacity(self: *Builder, scope: Metadata, file: Metadata, line: u32, column: u32) Metadata { +fn debugLexicalBlockAssumeCapacity( + self: *Builder, + scope: ?Metadata, + file: ?Metadata, + line: u32, + column: u32, +) Metadata { assert(!self.strip); return self.metadataSimpleAssumeCapacity(.lexical_block, Metadata.LexicalBlock{ - .scope = scope, - .file = file, + .scope = .wrap(scope), + .file = .wrap(file), .line = line, .column = column, }); } -fn debugLocationAssumeCapacity(self: *Builder, line: u32, column: u32, scope: Metadata, inlined_at: Metadata) Metadata { +fn debugLocationAssumeCapacity( + self: *Builder, + line: u32, + column: u32, + scope: Metadata, + inlined_at: ?Metadata, +) Metadata { assert(!self.strip); return self.metadataSimpleAssumeCapacity(.location, Metadata.Location{ .line = line, .column = column, .scope = scope, - .inlined_at = inlined_at, + .inlined_at = .wrap(inlined_at), }); } -fn debugBoolTypeAssumeCapacity(self: *Builder, name: MetadataString, size_in_bits: u64) Metadata { +fn debugBoolTypeAssumeCapacity(self: *Builder, name: ?Metadata.String, size_in_bits: u64) Metadata { assert(!self.strip); return self.metadataSimpleAssumeCapacity(.basic_bool_type, Metadata.BasicType{ - .name = name, + .name = .wrap(name), .size_in_bits_lo = @truncate(size_in_bits), .size_in_bits_hi = @truncate(size_in_bits >> 32), }); } -fn debugUnsignedTypeAssumeCapacity(self: *Builder, name: MetadataString, size_in_bits: u64) Metadata { +fn debugUnsignedTypeAssumeCapacity(self: *Builder, name: ?Metadata.String, size_in_bits: u64) Metadata { assert(!self.strip); return self.metadataSimpleAssumeCapacity(.basic_unsigned_type, Metadata.BasicType{ - .name = name, + .name = .wrap(name), .size_in_bits_lo = @truncate(size_in_bits), .size_in_bits_hi = @truncate(size_in_bits >> 32), }); } -fn debugSignedTypeAssumeCapacity(self: *Builder, name: MetadataString, size_in_bits: u64) Metadata { +fn debugSignedTypeAssumeCapacity(self: *Builder, name: ?Metadata.String, size_in_bits: u64) Metadata { assert(!self.strip); return self.metadataSimpleAssumeCapacity(.basic_signed_type, Metadata.BasicType{ - .name = name, + .name = .wrap(name), .size_in_bits_lo = @truncate(size_in_bits), .size_in_bits_hi = @truncate(size_in_bits >> 32), }); } -fn debugFloatTypeAssumeCapacity(self: *Builder, name: MetadataString, size_in_bits: u64) Metadata { +fn debugFloatTypeAssumeCapacity(self: *Builder, name: ?Metadata.String, size_in_bits: u64) Metadata { assert(!self.strip); return self.metadataSimpleAssumeCapacity(.basic_float_type, Metadata.BasicType{ - .name = name, + .name = .wrap(name), .size_in_bits_lo = @truncate(size_in_bits), .size_in_bits_hi = @truncate(size_in_bits >> 32), }); @@ -12616,21 +12681,21 @@ fn debugFloatTypeAssumeCapacity(self: *Builder, name: MetadataString, size_in_bi fn debugForwardReferenceAssumeCapacity(self: *Builder) Metadata { assert(!self.strip); - const index = Metadata.first_forward_reference + self.metadata_forward_references.items.len; + const index = self.metadata_forward_references.items.len; self.metadata_forward_references.appendAssumeCapacity(.none); - return @enumFromInt(index); + return .{ .index = @intCast(index), .kind = .forward }; } fn debugStructTypeAssumeCapacity( self: *Builder, - name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - underlying_type: Metadata, + underlying_type: ?Metadata, size_in_bits: u64, align_in_bits: u64, - fields_tuple: Metadata, + fields_tuple: ?Metadata, ) Metadata { assert(!self.strip); return self.debugCompositeTypeAssumeCapacity( @@ -12648,14 +12713,14 @@ fn debugStructTypeAssumeCapacity( fn debugUnionTypeAssumeCapacity( self: *Builder, - name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - underlying_type: Metadata, + underlying_type: ?Metadata, size_in_bits: u64, align_in_bits: u64, - fields_tuple: Metadata, + fields_tuple: ?Metadata, ) Metadata { assert(!self.strip); return self.debugCompositeTypeAssumeCapacity( @@ -12673,14 +12738,14 @@ fn debugUnionTypeAssumeCapacity( fn debugEnumerationTypeAssumeCapacity( self: *Builder, - name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - underlying_type: Metadata, + underlying_type: ?Metadata, size_in_bits: u64, align_in_bits: u64, - fields_tuple: Metadata, + fields_tuple: ?Metadata, ) Metadata { assert(!self.strip); return self.debugCompositeTypeAssumeCapacity( @@ -12698,14 +12763,14 @@ fn debugEnumerationTypeAssumeCapacity( fn debugArrayTypeAssumeCapacity( self: *Builder, - name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - underlying_type: Metadata, + underlying_type: ?Metadata, size_in_bits: u64, align_in_bits: u64, - fields_tuple: Metadata, + fields_tuple: ?Metadata, ) Metadata { assert(!self.strip); return self.debugCompositeTypeAssumeCapacity( @@ -12723,14 +12788,14 @@ fn debugArrayTypeAssumeCapacity( fn debugVectorTypeAssumeCapacity( self: *Builder, - name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - underlying_type: Metadata, + underlying_type: ?Metadata, size_in_bits: u64, align_in_bits: u64, - fields_tuple: Metadata, + fields_tuple: ?Metadata, ) Metadata { assert(!self.strip); return self.debugCompositeTypeAssumeCapacity( @@ -12749,48 +12814,48 @@ fn debugVectorTypeAssumeCapacity( fn debugCompositeTypeAssumeCapacity( self: *Builder, tag: Metadata.Tag, - name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - underlying_type: Metadata, + underlying_type: ?Metadata, size_in_bits: u64, align_in_bits: u64, - fields_tuple: Metadata, + fields_tuple: ?Metadata, ) Metadata { assert(!self.strip); return self.metadataSimpleAssumeCapacity(tag, Metadata.CompositeType{ - .name = name, - .file = file, - .scope = scope, + .name = .wrap(name), + .file = .wrap(file), + .scope = .wrap(scope), .line = line, - .underlying_type = underlying_type, + .underlying_type = .wrap(underlying_type), .size_in_bits_lo = @truncate(size_in_bits), .size_in_bits_hi = @truncate(size_in_bits >> 32), .align_in_bits_lo = @truncate(align_in_bits), .align_in_bits_hi = @truncate(align_in_bits >> 32), - .fields_tuple = fields_tuple, + .fields_tuple = .wrap(fields_tuple), }); } fn debugPointerTypeAssumeCapacity( self: *Builder, - name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - underlying_type: Metadata, + underlying_type: ?Metadata, size_in_bits: u64, align_in_bits: u64, offset_in_bits: u64, ) Metadata { assert(!self.strip); return self.metadataSimpleAssumeCapacity(.derived_pointer_type, Metadata.DerivedType{ - .name = name, - .file = file, - .scope = scope, + .name = .wrap(name), + .file = .wrap(file), + .scope = .wrap(scope), .line = line, - .underlying_type = underlying_type, + .underlying_type = .wrap(underlying_type), .size_in_bits_lo = @truncate(size_in_bits), .size_in_bits_hi = @truncate(size_in_bits >> 32), .align_in_bits_lo = @truncate(align_in_bits), @@ -12802,22 +12867,22 @@ fn debugPointerTypeAssumeCapacity( fn debugMemberTypeAssumeCapacity( self: *Builder, - name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - underlying_type: Metadata, + underlying_type: ?Metadata, size_in_bits: u64, align_in_bits: u64, offset_in_bits: u64, ) Metadata { assert(!self.strip); return self.metadataSimpleAssumeCapacity(.derived_member_type, Metadata.DerivedType{ - .name = name, - .file = file, - .scope = scope, + .name = .wrap(name), + .file = .wrap(file), + .scope = .wrap(scope), .line = line, - .underlying_type = underlying_type, + .underlying_type = .wrap(underlying_type), .size_in_bits_lo = @truncate(size_in_bits), .size_in_bits_hi = @truncate(size_in_bits >> 32), .align_in_bits_lo = @truncate(align_in_bits), @@ -12827,19 +12892,16 @@ fn debugMemberTypeAssumeCapacity( }); } -fn debugSubroutineTypeAssumeCapacity( - self: *Builder, - types_tuple: Metadata, -) Metadata { +fn debugSubroutineTypeAssumeCapacity(self: *Builder, types_tuple: ?Metadata) Metadata { assert(!self.strip); return self.metadataSimpleAssumeCapacity(.subroutine_type, Metadata.SubroutineType{ - .types_tuple = types_tuple, + .types_tuple = .wrap(types_tuple), }); } fn debugEnumeratorAssumeCapacity( self: *Builder, - name: MetadataString, + name: ?Metadata.String, unsigned: bool, bit_width: u32, value: std.math.big.int.Const, @@ -12847,7 +12909,7 @@ fn debugEnumeratorAssumeCapacity( assert(!self.strip); const Key = struct { tag: Metadata.Tag, - name: MetadataString, + name: Metadata.String.Optional, bit_width: u32, value: std.math.big.int.Const, }; @@ -12886,15 +12948,12 @@ fn debugEnumeratorAssumeCapacity( assert(!(tag == .enumerator_unsigned and !value.positive)); - const gop = self.metadata_map.getOrPutAssumeCapacityAdapted( - Key{ - .tag = tag, - .name = name, - .bit_width = bit_width, - .value = value, - }, - Adapter{ .builder = self }, - ); + const gop = self.metadata_map.getOrPutAssumeCapacityAdapted(Key{ + .tag = tag, + .name = .wrap(name), + .bit_width = bit_width, + .value = value, + }, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; @@ -12902,7 +12961,7 @@ fn debugEnumeratorAssumeCapacity( self.metadata_items.appendAssumeCapacity(.{ .tag = tag, .data = self.addMetadataExtraAssumeCapacity(Metadata.Enumerator{ - .name = name, + .name = .wrap(name), .bit_width = bit_width, .limbs_index = @intCast(self.metadata_limbs.items.len), .limbs_len = @intCast(value.limbs.len), @@ -12910,25 +12969,18 @@ fn debugEnumeratorAssumeCapacity( }); self.metadata_limbs.appendSliceAssumeCapacity(value.limbs); } - return @enumFromInt(gop.index); + return .{ .index = @intCast(gop.index), .kind = .node }; } -fn debugSubrangeAssumeCapacity( - self: *Builder, - lower_bound: Metadata, - count: Metadata, -) Metadata { +fn debugSubrangeAssumeCapacity(self: *Builder, lower_bound: ?Metadata, count: ?Metadata) Metadata { assert(!self.strip); return self.metadataSimpleAssumeCapacity(.subrange, Metadata.Subrange{ - .lower_bound = lower_bound, - .count = count, + .lower_bound = .wrap(lower_bound), + .count = .wrap(count), }); } -fn debugExpressionAssumeCapacity( - self: *Builder, - elements: []const u32, -) Metadata { +fn debugExpressionAssumeCapacity(self: *Builder, elements: []const u32) Metadata { assert(!self.strip); const Key = struct { elements: []const u32, @@ -12936,13 +12988,15 @@ fn debugExpressionAssumeCapacity( const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: Key) u32 { - var hasher = comptime std.hash.Wyhash.init(std.hash.int(@intFromEnum(Metadata.Tag.expression))); + var hasher = + comptime std.hash.Wyhash.init(std.hash.int(@intFromEnum(Metadata.Tag.expression))); hasher.update(std.mem.sliceAsBytes(key.elements)); return @truncate(hasher.final()); } pub fn eql(ctx: @This(), lhs_key: Key, _: void, rhs_index: usize) bool { - if (Metadata.Tag.expression != ctx.builder.metadata_items.items(.tag)[rhs_index]) return false; + if (Metadata.Tag.expression != ctx.builder.metadata_items.items(.tag)[rhs_index]) + return false; const rhs_data = ctx.builder.metadata_items.items(.data)[rhs_index]; var rhs_extra = ctx.builder.metadataExtraDataTrail(Metadata.Expression, rhs_data); return std.mem.eql( @@ -12969,15 +13023,12 @@ fn debugExpressionAssumeCapacity( }); self.metadata_extra.appendSliceAssumeCapacity(@ptrCast(elements)); } - return @enumFromInt(gop.index); + return .{ .index = @intCast(gop.index), .kind = .node }; } -fn metadataTupleAssumeCapacity( - self: *Builder, - elements: []const Metadata, -) Metadata { +fn metadataTupleOptionalsAssumeCapacity(self: *Builder, elements: []const Metadata.Optional) Metadata { const Key = struct { - elements: []const Metadata, + elements: []const Metadata.Optional, }; const Adapter = struct { builder: *const Builder, @@ -12992,9 +13043,9 @@ fn metadataTupleAssumeCapacity( const rhs_data = ctx.builder.metadata_items.items(.data)[rhs_index]; var rhs_extra = ctx.builder.metadataExtraDataTrail(Metadata.Tuple, rhs_data); return std.mem.eql( - Metadata, + Metadata.Optional, lhs_key.elements, - rhs_extra.trail.next(rhs_extra.data.elements_len, Metadata, ctx.builder), + rhs_extra.trail.next(rhs_extra.data.elements_len, Metadata.Optional, ctx.builder), ); } }; @@ -13015,117 +13066,55 @@ fn metadataTupleAssumeCapacity( }); self.metadata_extra.appendSliceAssumeCapacity(@ptrCast(elements)); } - return @enumFromInt(gop.index); -} - -fn strTupleAssumeCapacity( - self: *Builder, - str: MetadataString, - elements: []const Metadata, -) Metadata { - const Key = struct { - str: MetadataString, - elements: []const Metadata, - }; - const Adapter = struct { - builder: *const Builder, - pub fn hash(_: @This(), key: Key) u32 { - var hasher = comptime std.hash.Wyhash.init(std.hash.int(@intFromEnum(Metadata.Tag.tuple))); - hasher.update(std.mem.sliceAsBytes(key.elements)); - return @truncate(hasher.final()); - } - - pub fn eql(ctx: @This(), lhs_key: Key, _: void, rhs_index: usize) bool { - if (.str_tuple != ctx.builder.metadata_items.items(.tag)[rhs_index]) return false; - const rhs_data = ctx.builder.metadata_items.items(.data)[rhs_index]; - var rhs_extra = ctx.builder.metadataExtraDataTrail(Metadata.StrTuple, rhs_data); - return rhs_extra.data.str == lhs_key.str and std.mem.eql( - Metadata, - lhs_key.elements, - rhs_extra.trail.next(rhs_extra.data.elements_len, Metadata, ctx.builder), - ); - } - }; - - const gop = self.metadata_map.getOrPutAssumeCapacityAdapted( - Key{ .str = str, .elements = elements }, - Adapter{ .builder = self }, - ); - - if (!gop.found_existing) { - gop.key_ptr.* = {}; - gop.value_ptr.* = {}; - self.metadata_items.appendAssumeCapacity(.{ - .tag = .str_tuple, - .data = self.addMetadataExtraAssumeCapacity(Metadata.StrTuple{ - .str = str, - .elements_len = @intCast(elements.len), - }), - }); - self.metadata_extra.appendSliceAssumeCapacity(@ptrCast(elements)); - } - return @enumFromInt(gop.index); -} - -fn metadataModuleFlagAssumeCapacity( - self: *Builder, - behavior: Metadata, - name: MetadataString, - constant: Metadata, -) Metadata { - return self.metadataSimpleAssumeCapacity(.module_flag, Metadata.ModuleFlag{ - .behavior = behavior, - .name = name, - .constant = constant, - }); + return .{ .index = @intCast(gop.index), .kind = .node }; } fn debugLocalVarAssumeCapacity( self: *Builder, - name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - ty: Metadata, + ty: ?Metadata, ) Metadata { assert(!self.strip); return self.metadataSimpleAssumeCapacity(.local_var, Metadata.LocalVar{ - .name = name, - .file = file, - .scope = scope, + .name = .wrap(name), + .file = .wrap(file), + .scope = .wrap(scope), .line = line, - .ty = ty, + .ty = .wrap(ty), }); } fn debugParameterAssumeCapacity( self: *Builder, - name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - ty: Metadata, + ty: ?Metadata, arg_no: u32, ) Metadata { assert(!self.strip); return self.metadataSimpleAssumeCapacity(.parameter, Metadata.Parameter{ - .name = name, - .file = file, - .scope = scope, + .name = .wrap(name), + .file = .wrap(file), + .scope = .wrap(scope), .line = line, - .ty = ty, + .ty = .wrap(ty), .arg_no = arg_no, }); } fn debugGlobalVarAssumeCapacity( self: *Builder, - name: MetadataString, - linkage_name: MetadataString, - file: Metadata, - scope: Metadata, + name: ?Metadata.String, + linkage_name: ?Metadata.String, + file: ?Metadata, + scope: ?Metadata, line: u32, - ty: Metadata, + ty: ?Metadata, variable: Variable.Index, options: Metadata.GlobalVar.Options, ) Metadata { @@ -13133,12 +13122,12 @@ fn debugGlobalVarAssumeCapacity( return self.metadataDistinctAssumeCapacity( if (options.local) .@"global_var local" else .global_var, Metadata.GlobalVar{ - .name = name, - .linkage_name = linkage_name, - .file = file, - .scope = scope, + .name = .wrap(name), + .linkage_name = .wrap(linkage_name), + .file = .wrap(file), + .scope = .wrap(scope), .line = line, - .ty = ty, + .ty = .wrap(ty), .variable = variable, }, ); @@ -13146,13 +13135,13 @@ fn debugGlobalVarAssumeCapacity( fn debugGlobalVarExpressionAssumeCapacity( self: *Builder, - variable: Metadata, - expression: Metadata, + variable: ?Metadata, + expression: ?Metadata, ) Metadata { assert(!self.strip); return self.metadataSimpleAssumeCapacity(.global_var_expression, Metadata.GlobalVarExpression{ - .variable = variable, - .expression = expression, + .variable = .wrap(variable), + .expression = .wrap(expression), }); } @@ -13185,7 +13174,7 @@ fn metadataConstantAssumeCapacity(self: *Builder, constant: Constant) Metadata { .data = @intFromEnum(constant), }); } - return @enumFromInt(gop.index); + return .{ .index = @intCast(gop.index), .kind = .node }; } pub const Producer = struct { @@ -13209,8 +13198,8 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco // IDENTIFICATION_BLOCK { - const Identification = ir.Identification; - var identification_block = try bitcode.enterTopBlock(Identification); + const IdentificationBlock = ir.IdentificationBlock; + var identification_block = try bitcode.enterTopBlock(IdentificationBlock); const producer_str = try std.fmt.allocPrint(self.gpa, "{s} {d}.{d}.{d}", .{ producer.name, @@ -13220,42 +13209,42 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco }); defer self.gpa.free(producer_str); - try identification_block.writeAbbrev(Identification.Version{ .string = producer_str }); - try identification_block.writeAbbrev(Identification.Epoch{ .epoch = 0 }); + try identification_block.writeAbbrev(IdentificationBlock.Version{ .string = producer_str }); + try identification_block.writeAbbrev(IdentificationBlock.Epoch{ .epoch = 0 }); try identification_block.end(); } // MODULE_BLOCK { - const Module = ir.Module; - var module_block = try bitcode.enterTopBlock(Module); + const ModuleBlock = ir.ModuleBlock; + var module_block = try bitcode.enterTopBlock(ModuleBlock); - try module_block.writeAbbrev(Module.Version{}); + try module_block.writeAbbrev(ModuleBlock.Version{}); if (self.target_triple.slice(self)) |triple| { - try module_block.writeAbbrev(Module.String{ + try module_block.writeAbbrev(ModuleBlock.String{ .code = 2, .string = triple, }); } if (self.data_layout.slice(self)) |data_layout| { - try module_block.writeAbbrev(Module.String{ + try module_block.writeAbbrev(ModuleBlock.String{ .code = 3, .string = data_layout, }); } if (self.source_filename.slice(self)) |source_filename| { - try module_block.writeAbbrev(Module.String{ + try module_block.writeAbbrev(ModuleBlock.String{ .code = 16, .string = source_filename, }); } if (self.module_asm.items.len != 0) { - try module_block.writeAbbrev(Module.String{ + try module_block.writeAbbrev(ModuleBlock.String{ .code = 4, .string = self.module_asm.items, }); @@ -13263,16 +13252,17 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco // TYPE_BLOCK { - var type_block = try module_block.enterSubBlock(ir.Type, true); + const TypeBlock = ir.ModuleBlock.TypeBlock; + var type_block = try module_block.enterSubBlock(TypeBlock, true); - try type_block.writeAbbrev(ir.Type.NumEntry{ .num = @intCast(self.type_items.items.len) }); + try type_block.writeAbbrev(TypeBlock.NumEntry{ .num = @intCast(self.type_items.items.len) }); for (self.type_items.items, 0..) |item, i| { const ty: Type = @enumFromInt(i); switch (item.tag) { - .simple => try type_block.writeAbbrev(ir.Type.Simple{ .code = @truncate(item.data) }), - .integer => try type_block.writeAbbrev(ir.Type.Integer{ .width = item.data }), + .simple => try type_block.writeAbbrev(TypeBlock.Simple{ .code = @enumFromInt(item.data) }), + .integer => try type_block.writeAbbrev(TypeBlock.Integer{ .width = item.data }), .structure, .packed_structure, => |kind| { @@ -13282,19 +13272,19 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco else => unreachable, }; var extra = self.typeExtraDataTrail(Type.Structure, item.data); - try type_block.writeAbbrev(ir.Type.StructAnon{ + try type_block.writeAbbrev(TypeBlock.StructAnon{ .is_packed = is_packed, .types = extra.trail.next(extra.data.fields_len, Type, self), }); }, .named_structure => { const extra = self.typeExtraData(Type.NamedStructure, item.data); - try type_block.writeAbbrev(ir.Type.StructName{ + try type_block.writeAbbrev(TypeBlock.StructName{ .string = extra.id.slice(self).?, }); switch (extra.body) { - .none => try type_block.writeAbbrev(ir.Type.Opaque{}), + .none => try type_block.writeAbbrev(TypeBlock.Opaque{}), else => { const real_struct = self.type_items.items[@intFromEnum(extra.body)]; const is_packed: bool = switch (real_struct.tag) { @@ -13304,7 +13294,7 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco }; var real_extra = self.typeExtraDataTrail(Type.Structure, real_struct.data); - try type_block.writeAbbrev(ir.Type.StructNamed{ + try type_block.writeAbbrev(TypeBlock.StructNamed{ .is_packed = is_packed, .types = real_extra.trail.next(real_extra.data.fields_len, Type, self), }); @@ -13313,29 +13303,29 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco }, .array, .small_array, - => try type_block.writeAbbrev(ir.Type.Array{ + => try type_block.writeAbbrev(TypeBlock.Array{ .len = ty.aggregateLen(self), .child = ty.childType(self), }), .vector, .scalable_vector, - => try type_block.writeAbbrev(ir.Type.Vector{ + => try type_block.writeAbbrev(TypeBlock.Vector{ .len = ty.aggregateLen(self), .child = ty.childType(self), }), - .pointer => try type_block.writeAbbrev(ir.Type.Pointer{ + .pointer => try type_block.writeAbbrev(TypeBlock.Pointer{ .addr_space = ty.pointerAddrSpace(self), }), .target => { var extra = self.typeExtraDataTrail(Type.Target, item.data); - try type_block.writeAbbrev(ir.Type.StructName{ + try type_block.writeAbbrev(TypeBlock.StructName{ .string = extra.data.name.slice(self).?, }); const types = extra.trail.next(extra.data.types_len, Type, self); const ints = extra.trail.next(extra.data.ints_len, u32, self); - try type_block.writeAbbrev(ir.Type.Target{ + try type_block.writeAbbrev(TypeBlock.Target{ .num_types = extra.data.types_len, .types = types, .ints = ints, @@ -13348,7 +13338,7 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco else => unreachable, }; var extra = self.typeExtraDataTrail(Type.Function, item.data); - try type_block.writeAbbrev(ir.Type.Function{ + try type_block.writeAbbrev(TypeBlock.Function{ .is_vararg = is_vararg, .return_type = extra.data.ret, .param_types = extra.trail.next(extra.data.params_len, Type, self), @@ -13368,9 +13358,9 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco // PARAMATTR_GROUP_BLOCK { - const ParamattrGroup = ir.ParamattrGroup; + const ParamattrGroupBlock = ir.ModuleBlock.ParamattrGroupBlock; - var paramattr_group_block = try module_block.enterSubBlock(ParamattrGroup, true); + var paramattr_group_block = try module_block.enterSubBlock(ParamattrGroupBlock, true); for (self.function_attributes_set.keys()) |func_attributes| { for (func_attributes.slice(self), 0..) |attributes, i| { @@ -13572,8 +13562,8 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco // PARAMATTR_BLOCK { - const Paramattr = ir.Paramattr; - var paramattr_block = try module_block.enterSubBlock(Paramattr, true); + const ParamattrBlock = ir.ModuleBlock.ParamattrBlock; + var paramattr_block = try module_block.enterSubBlock(ParamattrBlock, true); for (self.function_attributes_set.keys()) |func_attributes| { const func_attributes_slice = func_attributes.slice(self); @@ -13590,7 +13580,7 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco record.appendAssumeCapacity(@intCast(group_index)); } - try paramattr_block.writeAbbrev(Paramattr.Entry{ .group_indices = record.items }); + try paramattr_block.writeAbbrev(ParamattrBlock.Entry{ .group_indices = record.items }); } try paramattr_block.end(); @@ -13624,38 +13614,35 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco } const ConstantAdapter = struct { - const ConstantAdapter = @This(); builder: *const Builder, globals: *const std.AutoArrayHashMapUnmanaged(Global.Index, void), - pub fn get(adapter: @This(), param: anytype, comptime field_name: []const u8) @TypeOf(param) { - _ = field_name; + pub fn get(adapter: @This(), param: anytype) switch (@TypeOf(param)) { + Constant => u32, + else => |Param| Param, + } { return switch (@TypeOf(param)) { - Constant => @enumFromInt(adapter.getConstantIndex(param)), + Constant => adapter.getConstantIndex(param), else => param, }; } - pub fn getConstantIndex(adapter: ConstantAdapter, constant: Constant) u32 { + pub fn getConstantIndex(adapter: @This(), constant: Constant) u32 { return switch (constant.unwrap()) { .constant => |c| c + adapter.numGlobals(), .global => |global| @intCast(adapter.globals.getIndex(global.unwrap(adapter.builder)).?), }; } - pub fn numConstants(adapter: ConstantAdapter) u32 { + pub fn numConstants(adapter: @This()) u32 { return @intCast(adapter.globals.count() + adapter.builder.constant_items.len); } - pub fn numGlobals(adapter: ConstantAdapter) u32 { + pub fn numGlobals(adapter: @This()) u32 { return @intCast(adapter.globals.count()); } }; - - const constant_adapter = ConstantAdapter{ - .builder = self, - .globals = &globals, - }; + const constant_adapter: ConstantAdapter = .{ .builder = self, .globals = &globals }; // Globals { @@ -13670,7 +13657,7 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco if (variable.section == .none) break :blk 0; const gop = section_map.getOrPutAssumeCapacity(variable.section); if (!gop.found_existing) { - try module_block.writeAbbrev(Module.String{ + try module_block.writeAbbrev(ModuleBlock.String{ .code = 5, .string = variable.section.slice(self).?, }); @@ -13686,7 +13673,7 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco const strtab = variable.global.strtab(self); const global = variable.global.ptrConst(self); - try module_block.writeAbbrev(Module.Variable{ + try module_block.writeAbbrev(ModuleBlock.Variable{ .strtab_offset = strtab.offset, .strtab_size = strtab.size, .type_index = global.type, @@ -13717,7 +13704,7 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco if (func.section == .none) break :blk 0; const gop = section_map.getOrPutAssumeCapacity(func.section); if (!gop.found_existing) { - try module_block.writeAbbrev(Module.String{ + try module_block.writeAbbrev(ModuleBlock.String{ .code = 5, .string = func.section.slice(self).?, }); @@ -13733,7 +13720,7 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco const strtab = func.global.strtab(self); const global = func.global.ptrConst(self); - try module_block.writeAbbrev(Module.Function{ + try module_block.writeAbbrev(ModuleBlock.Function{ .strtab_offset = strtab.offset, .strtab_size = strtab.size, .type_index = global.type, @@ -13757,7 +13744,7 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco const strtab = alias.global.strtab(self); const global = alias.global.ptrConst(self); - try module_block.writeAbbrev(Module.Alias{ + try module_block.writeAbbrev(ModuleBlock.Alias{ .strtab_offset = strtab.offset, .strtab_size = strtab.size, .type_index = global.type, @@ -13775,8 +13762,8 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco // CONSTANTS_BLOCK { - const Constants = ir.Constants; - var constants_block = try module_block.enterSubBlock(Constants, true); + const ConstantsBlock = ir.ModuleBlock.ConstantsBlock; + var constants_block = try module_block.enterSubBlock(ConstantsBlock, true); var current_type: Type = .none; const tags = self.constant_items.items(.tag); @@ -13786,7 +13773,7 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco const constant: Constant = @enumFromInt(index); const constant_type = constant.typeOf(self); if (constant_type != current_type) { - try constants_block.writeAbbrev(Constants.SetType{ .type_id = constant_type }); + try constants_block.writeAbbrev(ConstantsBlock.SetType{ .type_id = constant_type }); current_type = constant_type; } const data = datas[index]; @@ -13794,9 +13781,9 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco .null, .zeroinitializer, .none, - => try constants_block.writeAbbrev(Constants.Null{}), - .undef => try constants_block.writeAbbrev(Constants.Undef{}), - .poison => try constants_block.writeAbbrev(Constants.Poison{}), + => try constants_block.writeAbbrev(ConstantsBlock.Null{}), + .undef => try constants_block.writeAbbrev(ConstantsBlock.Undef{}), + .poison => try constants_block.writeAbbrev(ConstantsBlock.Poison{}), .positive_integer, .negative_integer, => |tag| { @@ -13832,7 +13819,7 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco try constants_block.writeUnabbrev(5, record.items); continue; }; - try constants_block.writeAbbrev(Constants.Integer{ + try constants_block.writeAbbrev(ConstantsBlock.Integer{ .value = @bitCast(if (val >= 0) val << 1 | 0 else @@ -13841,17 +13828,17 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco }, .half, .bfloat, - => try constants_block.writeAbbrev(Constants.Half{ .value = @truncate(data) }), - .float => try constants_block.writeAbbrev(Constants.Float{ .value = data }), + => try constants_block.writeAbbrev(ConstantsBlock.Half{ .value = @truncate(data) }), + .float => try constants_block.writeAbbrev(ConstantsBlock.Float{ .value = data }), .double => { const extra = self.constantExtraData(Constant.Double, data); - try constants_block.writeAbbrev(Constants.Double{ + try constants_block.writeAbbrev(ConstantsBlock.Double{ .value = (@as(u64, extra.hi) << 32) | extra.lo, }); }, .x86_fp80 => { const extra = self.constantExtraData(Constant.Fp80, data); - try constants_block.writeAbbrev(Constants.Fp80{ + try constants_block.writeAbbrev(ConstantsBlock.Fp80{ .hi = @as(u64, extra.hi) << 48 | @as(u64, extra.lo_hi) << 16 | extra.lo_lo >> 16, .lo = @truncate(extra.lo_lo), @@ -13861,7 +13848,7 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco .ppc_fp128, => { const extra = self.constantExtraData(Constant.Fp128, data); - try constants_block.writeAbbrev(Constants.Fp128{ + try constants_block.writeAbbrev(ConstantsBlock.Fp128{ .lo = @as(u64, extra.lo_hi) << 32 | @as(u64, extra.lo_lo), .hi = @as(u64, extra.hi_hi) << 32 | @as(u64, extra.hi_lo), }); @@ -13876,35 +13863,35 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco const values = extra.trail.next(len, Constant, self); try constants_block.writeAbbrevAdapted( - Constants.Aggregate{ .values = values }, + ConstantsBlock.Aggregate{ .values = values }, constant_adapter, ); }, .splat => { - const ConstantsWriter = @TypeOf(constants_block); + const ConstantsBlockWriter = @TypeOf(constants_block); const extra = self.constantExtraData(Constant.Splat, data); const vector_len = extra.type.vectorLen(self); const c = constant_adapter.getConstantIndex(extra.value); try bitcode.writeBits( - ConstantsWriter.abbrevId(Constants.Aggregate), - ConstantsWriter.abbrev_len, + ConstantsBlockWriter.abbrevId(ConstantsBlock.Aggregate), + ConstantsBlockWriter.abbrev_len, ); - try bitcode.writeVBR(vector_len, 6); + try bitcode.writeVbr(vector_len, 6); for (0..vector_len) |_| { - try bitcode.writeBits(c, Constants.Aggregate.ops[1].array_fixed); + try bitcode.writeBits(c, ConstantsBlock.Aggregate.ops[1].array_fixed); } }, .string => { const str: String = @enumFromInt(data); if (str == .none) { - try constants_block.writeAbbrev(Constants.Null{}); + try constants_block.writeAbbrev(ConstantsBlock.Null{}); } else { const slice = str.slice(self).?; if (slice.len > 0 and slice[slice.len - 1] == 0) - try constants_block.writeAbbrev(Constants.CString{ .string = slice[0 .. slice.len - 1] }) + try constants_block.writeAbbrev(ConstantsBlock.CString{ .string = slice[0 .. slice.len - 1] }) else - try constants_block.writeAbbrev(Constants.String{ .string = slice }); + try constants_block.writeAbbrev(ConstantsBlock.String{ .string = slice }); } }, .bitcast, @@ -13914,7 +13901,7 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco .trunc, => |tag| { const extra = self.constantExtraData(Constant.Cast, data); - try constants_block.writeAbbrevAdapted(Constants.Cast{ + try constants_block.writeAbbrevAdapted(ConstantsBlock.Cast{ .type_index = extra.type, .val = extra.val, .opcode = tag.toCastOpcode(), @@ -13930,7 +13917,7 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco .xor, => |tag| { const extra = self.constantExtraData(Constant.Binary, data); - try constants_block.writeAbbrevAdapted(Constants.Binary{ + try constants_block.writeAbbrevAdapted(ConstantsBlock.Binary{ .opcode = tag.toBinaryOpcode(), .lhs = extra.lhs, .rhs = extra.rhs, @@ -14014,7 +14001,7 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco }, .blockaddress => { const extra = self.constantExtraData(Constant.BlockAddress, data); - try constants_block.writeAbbrev(Constants.BlockAddress{ + try constants_block.writeAbbrev(ConstantsBlock.BlockAddress{ .type_id = extra.function.typeOf(self), .function = constant_adapter.getConstantIndex(extra.function.toConst(self)), .block = @intFromEnum(extra.block), @@ -14024,10 +14011,10 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco .no_cfi, => |tag| { const function: Function.Index = @enumFromInt(data); - try constants_block.writeAbbrev(Constants.DsoLocalEquivalentOrNoCfi{ + try constants_block.writeAbbrev(ConstantsBlock.DsoLocalEquivalentOrNoCfi{ .code = switch (tag) { - .dso_local_equivalent => 27, - .no_cfi => 29, + .dso_local_equivalent => .DSO_LOCAL_EQUIVALENT, + .no_cfi => .NO_CFI_VALUE, else => unreachable, }, .type_id = function.typeOf(self), @@ -14042,7 +14029,7 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco // METADATA_KIND_BLOCK { - const MetadataKindBlock = ir.MetadataKindBlock; + const MetadataKindBlock = ir.ModuleBlock.MetadataKindBlock; var metadata_kind_block = try module_block.enterSubBlock(MetadataKindBlock, true); inline for (@typeInfo(ir.FixedMetadataKind).@"enum".fields) |field| { @@ -14059,95 +14046,85 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco } const MetadataAdapter = struct { - builder: *const Builder, constant_adapter: ConstantAdapter, - pub fn init( - builder: *const Builder, - const_adapter: ConstantAdapter, - ) @This() { - return .{ - .builder = builder, - .constant_adapter = const_adapter, - }; - } - - pub fn get(adapter: @This(), value: anytype, comptime field_name: []const u8) @TypeOf(value) { - _ = field_name; - const Ty = @TypeOf(value); - return switch (Ty) { - Metadata => @enumFromInt(adapter.getMetadataIndex(value)), - MetadataString => @enumFromInt(adapter.getMetadataStringIndex(value)), - Constant => @enumFromInt(adapter.constant_adapter.getConstantIndex(value)), - else => value, + pub fn get(adapter: @This(), param: anytype) switch (@TypeOf(param)) { + Metadata, Metadata.Optional, Metadata.String, Metadata.String.Optional, Constant => u32, + else => |Result| Result, + } { + return switch (@TypeOf(param)) { + Metadata => adapter.getMetadataIndex(param), + Metadata.Optional => adapter.getOptionalMetadataIndex(param), + Metadata.String => adapter.getMetadataIndex(param.toMetadata()), + Metadata.String.Optional => adapter.getOptionalMetadataIndex(param.toMetadata()), + Constant => adapter.constant_adapter.getConstantIndex(param), + else => param, }; } pub fn getMetadataIndex(adapter: @This(), metadata: Metadata) u32 { - if (metadata == .none) return 0; - return @intCast(adapter.builder.metadata_string_map.count() + - @intFromEnum(metadata.unwrap(adapter.builder)) - 1); + const builder = adapter.constant_adapter.builder; + const unwrapped_metadata = metadata.unwrap(builder); + return switch (unwrapped_metadata.kind) { + .string => unwrapped_metadata.index, + .node => @intCast(builder.metadata_string_map.count() + unwrapped_metadata.index), + .forward, .local => unreachable, + }; } - pub fn getMetadataStringIndex(_: @This(), metadata_string: MetadataString) u32 { - return @intFromEnum(metadata_string); + pub fn getOptionalMetadataIndex(adapter: @This(), metadata: Metadata.Optional) u32 { + return if (metadata.unwrap()) |m| 1 + adapter.getMetadataIndex(m) else 0; } }; - - const metadata_adapter = MetadataAdapter.init(self, constant_adapter); + const metadata_adapter: MetadataAdapter = .{ .constant_adapter = constant_adapter }; // METADATA_BLOCK { - const MetadataBlock = ir.MetadataBlock; + const MetadataBlock = ir.ModuleBlock.MetadataBlock; var metadata_block = try module_block.enterSubBlock(MetadataBlock, true); const MetadataBlockWriter = @TypeOf(metadata_block); - // Emit all MetadataStrings - if (self.metadata_string_map.count() > 1) { - const strings_offset, const strings_size = blk: { - var strings_offset: u32 = 0; - var strings_size: u32 = 0; - for (1..self.metadata_string_map.count()) |metadata_string_index| { - const metadata_string: MetadataString = @enumFromInt(metadata_string_index); - const slice = metadata_string.slice(self); - strings_offset += bitcode.bitsVBR(@as(u32, @intCast(slice.len)), 6); - strings_size += @intCast(slice.len * 8); - } - break :blk .{ - std.mem.alignForward(u32, strings_offset, 32) / 8, - std.mem.alignForward(u32, strings_size, 32) / 8, - }; + // Emit all Metadata.Strings + const strings_len: u32 = @intCast(self.metadata_string_map.count()); + if (strings_len > 0) { + const string_bytes_offset = string_bytes_offset: { + var string_bytes_bit_offset: u32 = 0; + for ( + self.metadata_string_indices.items[0..strings_len], + self.metadata_string_indices.items[1..], + ) |start, end| string_bytes_bit_offset += BitcodeWriter.bitsVbr(end - start, 6); + break :string_bytes_offset @divExact( + std.mem.alignForward(u32, string_bytes_bit_offset, 32), + 8, + ); }; + const string_bytes_len = + std.mem.alignForward(u32, @intCast(self.metadata_string_bytes.items.len), 4); try bitcode.writeBits( comptime MetadataBlockWriter.abbrevId(MetadataBlock.Strings), MetadataBlockWriter.abbrev_len, ); - try bitcode.writeVBR(@as(u32, @intCast(self.metadata_string_map.count() - 1)), 6); - try bitcode.writeVBR(strings_offset, 6); + try bitcode.writeVbr(strings_len, 6); + try bitcode.writeVbr(string_bytes_offset, 6); - try bitcode.writeVBR(strings_size + strings_offset, 6); + try bitcode.writeVbr(string_bytes_offset + string_bytes_len, 6); try bitcode.alignTo32(); - for (1..self.metadata_string_map.count()) |metadata_string_index| { - const metadata_string: MetadataString = @enumFromInt(metadata_string_index); - const slice = metadata_string.slice(self); - try bitcode.writeVBR(@as(u32, @intCast(slice.len)), 6); - } + for ( + self.metadata_string_indices.items[0..strings_len], + self.metadata_string_indices.items[1..], + ) |start, end| try bitcode.writeVbr(end - start, 6); try bitcode.writeBlob(self.metadata_string_bytes.items); } - for ( - self.metadata_items.items(.tag)[1..], - self.metadata_items.items(.data)[1..], - ) |tag, data| { + for (self.metadata_items.items(.tag), self.metadata_items.items(.data)) |tag, data| { record.clearRetainingCapacity(); switch (tag) { - .none => unreachable, .file => { const extra = self.metadataExtraData(Metadata.File, data); @@ -14209,13 +14186,12 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco }, .location => { const extra = self.metadataExtraData(Metadata.Location, data); - assert(extra.scope != .none); - try metadata_block.writeAbbrev(MetadataBlock.Location{ + try metadata_block.writeAbbrevAdapted(MetadataBlock.Location{ .line = extra.line, .column = extra.column, - .scope = metadata_adapter.getMetadataIndex(extra.scope) - 1, - .inlined_at = @enumFromInt(metadata_adapter.getMetadataIndex(extra.inlined_at)), - }); + .scope = extra.scope, + .inlined_at = extra.inlined_at, + }, metadata_adapter); }, .basic_bool_type, .basic_unsigned_type, @@ -14325,7 +14301,7 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco @bitCast(flags), )); record.appendAssumeCapacity(extra.bit_width); - record.appendAssumeCapacity(metadata_adapter.getMetadataStringIndex(extra.name)); + record.appendAssumeCapacity(metadata_adapter.getOptionalMetadataIndex(extra.name.toMetadata())); const limbs = record.addManyAsSliceAssumeCapacity(limbs_len); bigint.writeTwosComplement(std.mem.sliceAsBytes(limbs), .little); for (limbs) |*limb| { @@ -14335,7 +14311,7 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco else -%val << 1 | 1); } - try metadata_block.writeUnabbrev(@intFromEnum(MetadataBlock.Enumerator.id), record.items); + try metadata_block.writeUnabbrev(@intFromEnum(MetadataBlock.Code.ENUMERATOR), record.items); continue; }; try metadata_block.writeAbbrevAdapted(MetadataBlock.Enumerator{ @@ -14350,7 +14326,6 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco }, .subrange => { const extra = self.metadataExtraData(Metadata.Subrange, data); - try metadata_block.writeAbbrevAdapted(MetadataBlock.Subrange{ .count = extra.count, .lower_bound = extra.lower_bound, @@ -14358,48 +14333,19 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco }, .expression => { var extra = self.metadataExtraDataTrail(Metadata.Expression, data); - const elements = extra.trail.next(extra.data.elements_len, u32, self); - try metadata_block.writeAbbrevAdapted(MetadataBlock.Expression{ .elements = elements, }, metadata_adapter); }, .tuple => { var extra = self.metadataExtraDataTrail(Metadata.Tuple, data); - - const elements = extra.trail.next(extra.data.elements_len, Metadata, self); - + const elements = + extra.trail.next(extra.data.elements_len, Metadata.Optional, self); try metadata_block.writeAbbrevAdapted(MetadataBlock.Node{ .elements = elements, }, metadata_adapter); }, - .str_tuple => { - var extra = self.metadataExtraDataTrail(Metadata.StrTuple, data); - - const elements = extra.trail.next(extra.data.elements_len, Metadata, self); - - const all_elems = try self.gpa.alloc(Metadata, elements.len + 1); - defer self.gpa.free(all_elems); - all_elems[0] = @enumFromInt(metadata_adapter.getMetadataStringIndex(extra.data.str)); - for (elements, all_elems[1..]) |elem, *out_elem| { - out_elem.* = @enumFromInt(metadata_adapter.getMetadataIndex(elem)); - } - - try metadata_block.writeAbbrev(MetadataBlock.Node{ - .elements = all_elems, - }); - }, - .module_flag => { - const extra = self.metadataExtraData(Metadata.ModuleFlag, data); - try metadata_block.writeAbbrev(MetadataBlock.Node{ - .elements = &.{ - @enumFromInt(metadata_adapter.getMetadataIndex(extra.behavior)), - @enumFromInt(metadata_adapter.getMetadataStringIndex(extra.name)), - @enumFromInt(metadata_adapter.getMetadataIndex(extra.constant)), - }, - }); - }, .local_var => { const extra = self.metadataExtraData(Metadata.LocalVar, data); try metadata_block.writeAbbrevAdapted(MetadataBlock.LocalVar{ @@ -14454,37 +14400,28 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco // Write named metadata for (self.metadata_named.keys(), self.metadata_named.values()) |name, operands| { - const slice = name.slice(self); - try metadata_block.writeAbbrev(MetadataBlock.Name{ - .name = slice, - }); - - const elements = self.metadata_extra.items[operands.index..][0..operands.len]; - for (elements) |*e| { - e.* = metadata_adapter.getMetadataIndex(@enumFromInt(e.*)) - 1; - } - - try metadata_block.writeAbbrev(MetadataBlock.NamedNode{ - .elements = @ptrCast(elements), - }); + try metadata_block.writeAbbrev(MetadataBlock.Name{ .name = name.slice(self).? }); + try metadata_block.writeAbbrevAdapted(MetadataBlock.NamedNode{ + .elements = @ptrCast(self.metadata_extra.items[operands.index..][0..operands.len]), + }, metadata_adapter); } // Write global attached metadata { - for (globals.keys()) |global| { - const global_ptr = global.ptrConst(self); - if (global_ptr.dbg == .none) continue; + for (globals.keys()) |global_index| { + const global = global_index.ptrConst(self); + if (global.dbg.unwrap()) |dbg| { + switch (global.kind) { + .function => |f| if (f.ptrConst(self).instructions.len != 0) continue, + else => {}, + } - switch (global_ptr.kind) { - .function => |f| if (f.ptrConst(self).instructions.len != 0) continue, - else => {}, + try metadata_block.writeAbbrevAdapted(MetadataBlock.GlobalDeclAttachment{ + .value = global_index.toConst(), + .kind = .dbg, + .metadata = dbg, + }, metadata_adapter); } - - try metadata_block.writeAbbrev(MetadataBlock.GlobalDeclAttachment{ - .value = @enumFromInt(constant_adapter.getConstantIndex(global.toConst())), - .kind = .dbg, - .metadata = @enumFromInt(metadata_adapter.getMetadataIndex(global_ptr.dbg) - 1), - }); } } @@ -14493,10 +14430,10 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco // OPERAND_BUNDLE_TAGS_BLOCK { - const OperandBundleTags = ir.OperandBundleTags; - var operand_bundle_tags_block = try module_block.enterSubBlock(OperandBundleTags, true); + const OperandBundleTagsBlock = ir.ModuleBlock.OperandBundleTagsBlock; + var operand_bundle_tags_block = try module_block.enterSubBlock(OperandBundleTagsBlock, true); - try operand_bundle_tags_block.writeAbbrev(OperandBundleTags.OperandBundleTag{ + try operand_bundle_tags_block.writeAbbrev(OperandBundleTagsBlock.OperandBundleTag{ .tag = "cold", }); @@ -14505,26 +14442,34 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco // Block info { - const BlockInfo = ir.BlockInfo; - var block_info_block = try module_block.enterSubBlock(BlockInfo, true); + const BlockInfoBlock = ir.BlockInfoBlock; + var block_info_block = try module_block.enterSubBlock(BlockInfoBlock, true); - try block_info_block.writeUnabbrev(BlockInfo.set_block_id, &.{ir.FunctionBlock.id}); - inline for (ir.FunctionBlock.abbrevs) |abbrev| { + try block_info_block.writeUnabbrev(BlockInfoBlock.set_block_id, &.{ + @intFromEnum(ir.ModuleBlock.FunctionBlock.id), + }); + inline for (ir.ModuleBlock.FunctionBlock.abbrevs) |abbrev| { try block_info_block.defineAbbrev(&abbrev.ops); } - try block_info_block.writeUnabbrev(BlockInfo.set_block_id, &.{ir.FunctionValueSymbolTable.id}); - inline for (ir.FunctionValueSymbolTable.abbrevs) |abbrev| { + try block_info_block.writeUnabbrev(BlockInfoBlock.set_block_id, &.{ + @intFromEnum(ir.ModuleBlock.FunctionBlock.ValueSymtabBlock.id), + }); + inline for (ir.ModuleBlock.FunctionBlock.ValueSymtabBlock.abbrevs) |abbrev| { try block_info_block.defineAbbrev(&abbrev.ops); } - try block_info_block.writeUnabbrev(BlockInfo.set_block_id, &.{ir.FunctionMetadataBlock.id}); - inline for (ir.FunctionMetadataBlock.abbrevs) |abbrev| { + try block_info_block.writeUnabbrev(BlockInfoBlock.set_block_id, &.{ + @intFromEnum(ir.ModuleBlock.FunctionBlock.MetadataBlock.id), + }); + inline for (ir.ModuleBlock.FunctionBlock.MetadataBlock.abbrevs) |abbrev| { try block_info_block.defineAbbrev(&abbrev.ops); } - try block_info_block.writeUnabbrev(BlockInfo.set_block_id, &.{ir.MetadataAttachmentBlock.id}); - inline for (ir.MetadataAttachmentBlock.abbrevs) |abbrev| { + try block_info_block.writeUnabbrev(BlockInfoBlock.set_block_id, &.{ + @intFromEnum(ir.ModuleBlock.FunctionBlock.MetadataAttachmentBlock.id), + }); + inline for (ir.ModuleBlock.FunctionBlock.MetadataAttachmentBlock.abbrevs) |abbrev| { try block_info_block.defineAbbrev(&abbrev.ops); } @@ -14534,38 +14479,40 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco // FUNCTION_BLOCKS { const FunctionAdapter = struct { - constant_adapter: ConstantAdapter, metadata_adapter: MetadataAdapter, func: *const Function, instruction_index: Function.Instruction.Index, - pub fn get(adapter: @This(), value: anytype, comptime field_name: []const u8) @TypeOf(value) { - _ = field_name; - const Ty = @TypeOf(value); - return switch (Ty) { - Value => @enumFromInt(adapter.getOffsetValueIndex(value)), - Constant => @enumFromInt(adapter.getOffsetConstantIndex(value)), - FunctionAttributes => @enumFromInt(switch (value) { + pub fn get(adapter: @This(), param: anytype) switch (@TypeOf(param)) { + Value, Constant, FunctionAttributes => u32, + else => |Result| Result, + } { + return switch (@TypeOf(param)) { + Value => adapter.getOffsetValueIndex(param), + Constant => adapter.getOffsetConstantIndex(param), + FunctionAttributes => switch (param) { .none => 0, - else => 1 + adapter.constant_adapter.builder.function_attributes_set.getIndex(value).?, - }), - else => value, + else => @intCast(1 + adapter.metadata_adapter.constant_adapter.builder + .function_attributes_set.getIndex(param).?), + }, + else => param, }; } pub fn getValueIndex(adapter: @This(), value: Value) u32 { return @intCast(switch (value.unwrap()) { .instruction => |instruction| instruction.valueIndex(adapter.func) + adapter.firstInstr(), - .constant => |constant| adapter.constant_adapter.getConstantIndex(constant), + .constant => |constant| adapter.metadata_adapter.constant_adapter.getConstantIndex(constant), .metadata => |metadata| { - const real_metadata = metadata.unwrap(adapter.metadata_adapter.builder); - if (@intFromEnum(real_metadata) < Metadata.first_local_metadata) - return adapter.metadata_adapter.getMetadataIndex(real_metadata) - 1; - - return @intCast(@intFromEnum(metadata) - - Metadata.first_local_metadata + - adapter.metadata_adapter.builder.metadata_string_map.count() - 1 + - adapter.metadata_adapter.builder.metadata_map.count() - 1); + const builder = adapter.metadata_adapter.constant_adapter.builder; + const unwrapped_metadata = metadata.unwrap(builder); + return switch (unwrapped_metadata.kind) { + .string, .node => adapter.metadata_adapter.getMetadataIndex(unwrapped_metadata), + .forward => unreachable, + .local => @intCast(builder.metadata_string_map.count() + + builder.metadata_map.count() + + unwrapped_metadata.index), + }; }, }); } @@ -14589,12 +14536,12 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco } fn firstInstr(adapter: @This()) u32 { - return adapter.constant_adapter.numConstants(); + return adapter.metadata_adapter.constant_adapter.numConstants(); } }; for (self.functions.items, 0..) |func, func_index| { - const FunctionBlock = ir.FunctionBlock; + const FunctionBlock = ir.ModuleBlock.FunctionBlock; if (func.global.getReplacement(self) != .none) continue; if (func.instructions.len == 0) continue; @@ -14604,7 +14551,6 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco try function_block.writeAbbrev(FunctionBlock.DeclareBlocks{ .num_blocks = func.blocks.len }); var adapter: FunctionAdapter = .{ - .constant_adapter = constant_adapter, .metadata_adapter = metadata_adapter, .func = &func, .instruction_index = @enumFromInt(0), @@ -14612,7 +14558,7 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco // Emit function level metadata block if (!func.strip and func.debug_values.len > 0) { - const MetadataBlock = ir.FunctionMetadataBlock; + const MetadataBlock = ir.ModuleBlock.FunctionBlock.MetadataBlock; var metadata_block = try function_block.enterSubBlock(MetadataBlock, false); for (func.debug_values) |value| { @@ -15048,7 +14994,7 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco const vals = extra.trail.next(extra.data.cases_len, Constant, &func); const blocks = extra.trail.next(extra.data.cases_len, Function.Block.Index, &func); for (vals, blocks) |val, block| { - record.appendAssumeCapacity(adapter.constant_adapter.getConstantIndex(val)); + record.appendAssumeCapacity(adapter.metadata_adapter.constant_adapter.getConstantIndex(val)); record.appendAssumeCapacity(@intFromEnum(block)); } @@ -15135,12 +15081,12 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco switch (debug_location) { .no_location => has_location = false, .location => |location| { - try function_block.writeAbbrev(FunctionBlock.DebugLoc{ + try function_block.writeAbbrevAdapted(FunctionBlock.DebugLoc{ .line = location.line, .column = location.column, - .scope = @enumFromInt(metadata_adapter.getMetadataIndex(location.scope)), - .inlined_at = @enumFromInt(metadata_adapter.getMetadataIndex(location.inlined_at)), - }); + .scope = location.scope, + .inlined_at = location.inlined_at, + }, metadata_adapter); has_location = true; }, } @@ -15152,16 +15098,16 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco // VALUE_SYMTAB if (!func.strip) { - const ValueSymbolTable = ir.FunctionValueSymbolTable; + const ValueSymtabBlock = ir.ModuleBlock.FunctionBlock.ValueSymtabBlock; - var value_symtab_block = try function_block.enterSubBlock(ValueSymbolTable, false); + var value_symtab_block = try function_block.enterSubBlock(ValueSymtabBlock, false); for (func.blocks, 0..) |block, block_index| { const name = block.instruction.name(&func); if (name == .none or name == .empty) continue; - try value_symtab_block.writeAbbrev(ValueSymbolTable.BlockEntry{ + try value_symtab_block.writeAbbrev(ValueSymtabBlock.BlockEntry{ .value_id = @intCast(block_index), .string = name.slice(self).?, }); @@ -15174,17 +15120,14 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco // METADATA_ATTACHMENT_BLOCK { - const MetadataAttachmentBlock = ir.MetadataAttachmentBlock; + const MetadataAttachmentBlock = ir.ModuleBlock.FunctionBlock.MetadataAttachmentBlock; var metadata_attach_block = try function_block.enterSubBlock(MetadataAttachmentBlock, false); - dbg: { - if (func.strip) break :dbg; - const dbg = func.global.ptrConst(self).dbg; - if (dbg == .none) break :dbg; - try metadata_attach_block.writeAbbrev(MetadataAttachmentBlock.AttachmentGlobalSingle{ + if (func.global.ptrConst(self).dbg.unwrap()) |dbg| { + try metadata_attach_block.writeAbbrevAdapted(MetadataAttachmentBlock.AttachmentGlobalSingle{ .kind = .dbg, - .metadata = @enumFromInt(metadata_adapter.getMetadataIndex(dbg) - 1), - }); + .metadata = dbg, + }, metadata_adapter); } var instr_index: u32 = 0; @@ -15201,16 +15144,16 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco }; switch (weights) { .none => {}, - .unpredictable => try metadata_attach_block.writeAbbrev(MetadataAttachmentBlock.AttachmentInstructionSingle{ + .unpredictable => try metadata_attach_block.writeAbbrevAdapted(MetadataAttachmentBlock.AttachmentInstructionSingle{ .inst = instr_index, .kind = .unpredictable, - .metadata = @enumFromInt(metadata_adapter.getMetadataIndex(.empty_tuple) - 1), - }), - _ => try metadata_attach_block.writeAbbrev(MetadataAttachmentBlock.AttachmentInstructionSingle{ + .metadata = .empty_tuple, + }, metadata_adapter), + _ => try metadata_attach_block.writeAbbrevAdapted(MetadataAttachmentBlock.AttachmentInstructionSingle{ .inst = instr_index, .kind = .prof, - .metadata = @enumFromInt(metadata_adapter.getMetadataIndex(@enumFromInt(@intFromEnum(weights))) - 1), - }), + .metadata = weights.toMetadata(), + }, metadata_adapter), } instr_index += 1; }, @@ -15228,7 +15171,7 @@ pub fn toBitcode(self: *Builder, allocator: Allocator, producer: Producer) bitco // STRTAB_BLOCK { - const Strtab = ir.Strtab; + const Strtab = ir.StrtabBlock; var strtab_block = try bitcode.enterTopBlock(Strtab); try strtab_block.writeAbbrev(Strtab.Blob{ .blob = self.strtab_string_bytes.items }); diff --git a/lib/std/zig/llvm/bitcode_writer.zig b/lib/std/zig/llvm/bitcode_writer.zig index 35bd880085..77102a8a7c 100644 --- a/lib/std/zig/llvm/bitcode_writer.zig +++ b/lib/std/zig/llvm/bitcode_writer.zig @@ -88,7 +88,7 @@ pub fn BitcodeWriter(comptime types: []const type) type { } } - pub fn writeVBR(self: *BcWriter, value: anytype, comptime vbr_bits: usize) Error!void { + 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))); @@ -110,7 +110,7 @@ pub fn BitcodeWriter(comptime types: []const type) type { try self.writeBits(in_buffer, vbr_bits); } - pub fn bitsVBR(_: *const BcWriter, value: anytype, comptime vbr_bits: usize) u16 { + 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))); @@ -177,8 +177,8 @@ pub fn BitcodeWriter(comptime types: []const type) type { 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.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 @@ -214,16 +214,16 @@ pub fn BitcodeWriter(comptime types: []const type) type { 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); + try self.bitcode.writeVbr(code, 6); + try self.bitcode.writeVbr(values.len, 6); for (values) |val| { - try self.bitcode.writeVBR(val, 6); + 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, comptime _: []const u8) @TypeOf(param) { + pub fn get(_: @This(), param: anytype) @TypeOf(param) { return param; } }{}); @@ -253,47 +253,45 @@ pub fn BitcodeWriter(comptime types: []const type) type { comptime var field_index: usize = 0; inline for (Abbrev.ops) |ty| { - const field_name = fields[field_index].name; - const param = @field(params, field_name); - + const param = @field(params, fields[field_index].name); switch (ty) { .literal => continue, - .fixed => |len| try self.bitcode.writeBits(adapter.get(param, field_name), len), + .fixed => |len| try self.bitcode.writeBits(adapter.get(param), len), .fixed_runtime => |width_ty| try self.bitcode.writeBits( - adapter.get(param, field_name), + adapter.get(param), self.bitcode.getTypeWidth(width_ty), ), - .vbr => |len| try self.bitcode.writeVBR(adapter.get(param, field_name), len), - .char6 => try self.bitcode.write6BitChar(adapter.get(param, field_name)), + .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.writeVbr(param.len, 6); try self.bitcode.writeBlob(param); }, .array_fixed => |len| { - try self.bitcode.writeVBR(param.len, 6); + try self.bitcode.writeVbr(param.len, 6); for (param) |x| { - try self.bitcode.writeBits(adapter.get(x, field_name), len); + try self.bitcode.writeBits(adapter.get(x), len); } }, .array_fixed_runtime => |width_ty| { - try self.bitcode.writeVBR(param.len, 6); + try self.bitcode.writeVbr(param.len, 6); for (param) |x| { try self.bitcode.writeBits( - adapter.get(x, field_name), + adapter.get(x), self.bitcode.getTypeWidth(width_ty), ); } }, .array_vbr => |len| { - try self.bitcode.writeVBR(param.len, 6); + try self.bitcode.writeVbr(param.len, 6); for (param) |x| { - try self.bitcode.writeVBR(adapter.get(x, field_name), len); + try self.bitcode.writeVbr(adapter.get(x), len); } }, .array_char6 => { - try self.bitcode.writeVBR(param.len, 6); + try self.bitcode.writeVbr(param.len, 6); for (param) |x| { - try self.bitcode.write6BitChar(adapter.get(x, field_name)); + try self.bitcode.write6BitChar(adapter.get(x)); } }, } @@ -307,7 +305,7 @@ pub fn BitcodeWriter(comptime types: []const type) type { try bitcode.writeBits(2, abbrev_len); // ops.len is not accurate because arrays are actually two ops - try bitcode.writeVBR(blk: { + try bitcode.writeVbr(blk: { var count: usize = 0; inline for (ops) |op| { count += switch (op) { @@ -322,22 +320,22 @@ pub fn BitcodeWriter(comptime types: []const type) type { switch (op) { .literal => |value| { try bitcode.writeBits(1, 1); - try bitcode.writeVBR(value, 8); + try bitcode.writeVbr(value, 8); }, .fixed => |width| { try bitcode.writeBits(0, 1); try bitcode.writeBits(1, 3); - try bitcode.writeVBR(width, 5); + 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); + 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); + try bitcode.writeVbr(width, 5); }, .char6 => { try bitcode.writeBits(0, 1); @@ -355,7 +353,7 @@ pub fn BitcodeWriter(comptime types: []const type) type { // Fixed or VBR op try bitcode.writeBits(0, 1); try bitcode.writeBits(1, 3); - try bitcode.writeVBR(width, 5); + try bitcode.writeVbr(width, 5); }, .array_fixed_runtime => |width_ty| { // Array op @@ -365,7 +363,7 @@ pub fn BitcodeWriter(comptime types: []const type) type { // Fixed or VBR op try bitcode.writeBits(0, 1); try bitcode.writeBits(1, 3); - try bitcode.writeVBR(bitcode.getTypeWidth(width_ty), 5); + try bitcode.writeVbr(bitcode.getTypeWidth(width_ty), 5); }, .array_vbr => |width| { // Array op @@ -375,7 +373,7 @@ pub fn BitcodeWriter(comptime types: []const type) type { // Fixed or VBR op try bitcode.writeBits(0, 1); try bitcode.writeBits(2, 3); - try bitcode.writeVBR(width, 5); + try bitcode.writeVbr(width, 5); }, .array_char6 => { // Array op diff --git a/lib/std/zig/llvm/ir.zig b/lib/std/zig/llvm/ir.zig index 824186efb8..13cc180d4d 100644 --- a/lib/std/zig/llvm/ir.zig +++ b/lib/std/zig/llvm/ir.zig @@ -21,9 +21,60 @@ const ColumnAbbrev = AbbrevOp{ .vbr = 8 }; const BlockAbbrev = AbbrevOp{ .vbr = 6 }; const BlockArrayAbbrev = AbbrevOp{ .array_vbr = 6 }; +/// All bitcode files can optionally include a BLOCKINFO block, which contains +/// metadata about other blocks in the file. +/// The only top-level block types are MODULE, IDENTIFICATION, STRTAB and SYMTAB. +pub const BlockId = enum(u5) { + /// BLOCKINFO_BLOCK is used to define metadata about blocks, for example, + /// standard abbrevs that should be available to all blocks of a specified + /// ID. + BLOCKINFO = 0, + + /// Blocks + MODULE = FIRST_APPLICATION, + + /// Module sub-block id's. + PARAMATTR, + PARAMATTR_GROUP, + + CONSTANTS, + FUNCTION, + + /// Block intended to contains information on the bitcode versioning. + /// Can be used to provide better error messages when we fail to parse a + /// bitcode file. + IDENTIFICATION, + + VALUE_SYMTAB, + METADATA, + METADATA_ATTACHMENT, + + TYPE, + + USELIST, + + MODULE_STRTAB, + GLOBALVAL_SUMMARY, + + OPERAND_BUNDLE_TAGS, + + METADATA_KIND, + + STRTAB, + + FULL_LTO_GLOBALVAL_SUMMARY, + + SYMTAB, + + SYNC_SCOPE_NAMES, + + /// Block IDs 1-7 are reserved for future expansion. + pub const FIRST_APPLICATION = 8; +}; + /// Unused tags are commented out so that they are omitted in the generated /// bitcode, which scans over this enum using reflection. -pub const FixedMetadataKind = enum(u8) { +pub const FixedMetadataKind = enum(u6) { dbg = 0, //tbaa = 1, prof = 2, @@ -66,138 +117,79 @@ pub const FixedMetadataKind = enum(u8) { //@"coro.outside.frame" = 39, }; -pub const MetadataCode = enum(u8) { - /// MDSTRING: [values] - STRING_OLD = 1, - /// VALUE: [type num, value num] - VALUE = 2, - /// NODE: [n x md num] - NODE = 3, - /// STRING: [values] - NAME = 4, - /// DISTINCT_NODE: [n x md num] - DISTINCT_NODE = 5, - /// [n x [id, name]] - KIND = 6, - /// [distinct, line, col, scope, inlined-at?] - LOCATION = 7, - /// OLD_NODE: [n x (type num, value num)] - OLD_NODE = 8, - /// OLD_FN_NODE: [n x (type num, value num)] - OLD_FN_NODE = 9, - /// NAMED_NODE: [n x mdnodes] - NAMED_NODE = 10, - /// [m x [value, [n x [id, mdnode]]] - ATTACHMENT = 11, - /// [distinct, tag, vers, header, n x md num] - GENERIC_DEBUG = 12, - /// [distinct, count, lo] - SUBRANGE = 13, - /// [isUnsigned|distinct, value, name] - ENUMERATOR = 14, - /// [distinct, tag, name, size, align, enc] - BASIC_TYPE = 15, - /// [distinct, filename, directory, checksumkind, checksum] - FILE = 16, - /// [distinct, ...] - DERIVED_TYPE = 17, - /// [distinct, ...] - COMPOSITE_TYPE = 18, - /// [distinct, flags, types, cc] - SUBROUTINE_TYPE = 19, - /// [distinct, ...] - COMPILE_UNIT = 20, - /// [distinct, ...] - SUBPROGRAM = 21, - /// [distinct, scope, file, line, column] - LEXICAL_BLOCK = 22, - ///[distinct, scope, file, discriminator] - LEXICAL_BLOCK_FILE = 23, - /// [distinct, scope, file, name, line, exportSymbols] - NAMESPACE = 24, - /// [distinct, scope, name, type, ...] - TEMPLATE_TYPE = 25, - /// [distinct, scope, name, type, value, ...] - TEMPLATE_VALUE = 26, - /// [distinct, ...] - GLOBAL_VAR = 27, - /// [distinct, ...] - LOCAL_VAR = 28, - /// [distinct, n x element] - EXPRESSION = 29, - /// [distinct, name, file, line, ...] - OBJC_PROPERTY = 30, - /// [distinct, tag, scope, entity, line, name] - IMPORTED_ENTITY = 31, - /// [distinct, scope, name, ...] - MODULE = 32, - /// [distinct, macinfo, line, name, value] - MACRO = 33, - /// [distinct, macinfo, line, file, ...] - MACRO_FILE = 34, - /// [count, offset] blob([lengths][chars]) - STRINGS = 35, - /// [valueid, n x [id, mdnode]] - GLOBAL_DECL_ATTACHMENT = 36, - /// [distinct, var, expr] - GLOBAL_VAR_EXPR = 37, - /// [offset] - INDEX_OFFSET = 38, - /// [bitpos] - INDEX = 39, - /// [distinct, scope, name, file, line] - LABEL = 40, - /// [distinct, name, size, align,...] - STRING_TYPE = 41, - /// [distinct, scope, name, variable,...] - COMMON_BLOCK = 44, - /// [distinct, count, lo, up, stride] - GENERIC_SUBRANGE = 45, - /// [n x [type num, value num]] - ARG_LIST = 46, - /// [distinct, ...] - ASSIGN_ID = 47, +pub const BlockInfoBlock = struct { + pub const id: BlockId = .BLOCKINFO; + + pub const set_block_id = 1; + + pub const abbrevs = [_]type{}; }; -pub const Identification = struct { - pub const id = 13; +/// MODULE blocks have a number of optional fields and subblocks. +pub const ModuleBlock = struct { + pub const id: BlockId = .MODULE; pub const abbrevs = [_]type{ - Version, - Epoch, + ModuleBlock.Version, + ModuleBlock.String, + ModuleBlock.Variable, + ModuleBlock.Function, + ModuleBlock.Alias, }; - pub const Version = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 1 }, - .{ .array_fixed = 8 }, - }; - string: []const u8, - }; + pub const Code = enum(u5) { + /// VERSION: [version#] + VERSION = 1, + /// TRIPLE: [strchr x N] + TRIPLE = 2, + /// DATALAYOUT: [strchr x N] + DATALAYOUT = 3, + /// ASM: [strchr x N] + ASM = 4, + /// SECTIONNAME: [strchr x N] + SECTIONNAME = 5, - pub const Epoch = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 2 }, - .{ .vbr = 6 }, - }; - epoch: u32, - }; -}; + /// Deprecated, but still needed to read old bitcode files. + /// DEPLIB: [strchr x N] + DEPLIB = 6, -pub const Module = struct { - pub const id = 8; + /// GLOBALVAR: [pointer type, isconst, initid, + /// linkage, alignment, section, visibility, threadlocal] + GLOBALVAR = 7, - pub const abbrevs = [_]type{ - Version, - String, - Variable, - Function, - Alias, + /// FUNCTION: [type, callingconv, isproto, linkage, paramattrs, alignment, + /// section, visibility, gc, unnamed_addr] + FUNCTION = 8, + + /// ALIAS: [alias type, aliasee val#, linkage, visibility] + ALIAS_OLD = 9, + + /// GCNAME: [strchr x N] + GCNAME = 11, + /// COMDAT: [selection_kind, name] + COMDAT = 12, + + /// VSTOFFSET: [offset] + VSTOFFSET = 13, + + /// ALIAS: [alias value type, addrspace, aliasee val#, linkage, visibility] + ALIAS = 14, + + METADATA_VALUES_UNUSED = 15, + + /// SOURCE_FILENAME: [namechar x N] + SOURCE_FILENAME = 16, + + /// HASH: [5*i32] + HASH = 17, + + /// IFUNC: [ifunc value type, addrspace, resolver val#, linkage, visibility] + IFUNC = 18, }; pub const Version = struct { pub const ops = [_]AbbrevOp{ - .{ .literal = 1 }, + .{ .literal = @intFromEnum(ModuleBlock.Code.VERSION) }, .{ .literal = 2 }, }; }; @@ -219,7 +211,7 @@ pub const Module = struct { }; pub const ops = [_]AbbrevOp{ - .{ .literal = 7 }, // Code + .{ .literal = @intFromEnum(ModuleBlock.Code.GLOBALVAR) }, // Code .{ .vbr = 16 }, // strtab_offset .{ .vbr = 16 }, // strtab_size .{ .fixed_runtime = Builder.Type }, @@ -255,7 +247,7 @@ pub const Module = struct { pub const Function = struct { pub const ops = [_]AbbrevOp{ - .{ .literal = 8 }, // Code + .{ .literal = @intFromEnum(ModuleBlock.Code.FUNCTION) }, // Code .{ .vbr = 16 }, // strtab_offset .{ .vbr = 16 }, // strtab_size .{ .fixed_runtime = Builder.Type }, @@ -294,7 +286,7 @@ pub const Module = struct { pub const Alias = struct { pub const ops = [_]AbbrevOp{ - .{ .literal = 14 }, // Code + .{ .literal = @intFromEnum(ModuleBlock.Code.ALIAS) }, // Code .{ .vbr = 16 }, // strtab_offset .{ .vbr = 16 }, // strtab_size .{ .fixed_runtime = Builder.Type }, @@ -319,1542 +311,1986 @@ pub const Module = struct { unnamed_addr: Builder.UnnamedAddr, preemption: Builder.Preemption, }; -}; - -pub const BlockInfo = struct { - pub const id = 0; - - pub const set_block_id = 1; - - pub const abbrevs = [_]type{}; -}; - -pub const Type = struct { - pub const id = 17; - - pub const abbrevs = [_]type{ - NumEntry, - Simple, - Opaque, - Integer, - StructAnon, - StructNamed, - StructName, - Array, - Vector, - Pointer, - Target, - Function, - }; - - pub const NumEntry = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 1 }, - .{ .fixed = 32 }, - }; - num: u32, - }; - - pub const Simple = struct { - pub const ops = [_]AbbrevOp{ - .{ .vbr = 4 }, - }; - code: u5, - }; - - pub const Opaque = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 6 }, - .{ .literal = 0 }, - }; - }; - pub const Integer = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 7 }, - .{ .fixed = 28 }, + /// PARAMATTR blocks have code for defining a parameter attribute set. + pub const ParamattrBlock = struct { + pub const id: BlockId = .PARAMATTR; + + pub const abbrevs = [_]type{ + ModuleBlock.ParamattrBlock.Entry, + }; + + pub const Code = enum(u2) { + /// Deprecated, but still needed to read old bitcode files. + /// ENTRY: [paramidx0, attr0, paramidx1, attr1...] + ENTRY_OLD = 1, + /// ENTRY: [attrgrp0, attrgrp1, ...] + ENTRY = 2, + }; + + pub const Entry = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ParamattrBlock.Code.ENTRY) }, + .{ .array_vbr = 8 }, + }; + group_indices: []const u64, + }; + }; + + pub const ParamattrGroupBlock = struct { + pub const id: BlockId = .PARAMATTR_GROUP; + + pub const abbrevs = [_]type{}; + + pub const Code = enum(u2) { + /// ENTRY: [grpid, idx, attr0, attr1, ...] + CODE_ENTRY = 3, + }; + }; + + /// The constants block (CONSTANTS_BLOCK_ID) describes emission for each + /// constant and maintains an implicit current type value. + pub const ConstantsBlock = struct { + pub const id: BlockId = .CONSTANTS; + + pub const abbrevs = [_]type{ + ModuleBlock.ConstantsBlock.SetType, + ModuleBlock.ConstantsBlock.Null, + ModuleBlock.ConstantsBlock.Undef, + ModuleBlock.ConstantsBlock.Poison, + ModuleBlock.ConstantsBlock.Integer, + ModuleBlock.ConstantsBlock.Half, + ModuleBlock.ConstantsBlock.Float, + ModuleBlock.ConstantsBlock.Double, + ModuleBlock.ConstantsBlock.Fp80, + ModuleBlock.ConstantsBlock.Fp128, + ModuleBlock.ConstantsBlock.Aggregate, + ModuleBlock.ConstantsBlock.String, + ModuleBlock.ConstantsBlock.CString, + ModuleBlock.ConstantsBlock.Cast, + ModuleBlock.ConstantsBlock.Binary, + ModuleBlock.ConstantsBlock.Cmp, + ModuleBlock.ConstantsBlock.ExtractElement, + ModuleBlock.ConstantsBlock.InsertElement, + ModuleBlock.ConstantsBlock.ShuffleVector, + ModuleBlock.ConstantsBlock.ShuffleVectorEx, + ModuleBlock.ConstantsBlock.BlockAddress, + ModuleBlock.ConstantsBlock.DsoLocalEquivalentOrNoCfi, + }; + + pub const Code = enum(u6) { + /// SETTYPE: [typeid] + SETTYPE = 1, + /// NULL + NULL = 2, + /// UNDEF + UNDEF = 3, + /// INTEGER: [intval] + INTEGER = 4, + /// WIDE_INTEGER: [n x intval] + WIDE_INTEGER = 5, + /// FLOAT: [fpval] + FLOAT = 6, + /// AGGREGATE: [n x value number] + AGGREGATE = 7, + /// STRING: [values] + STRING = 8, + /// CSTRING: [values] + CSTRING = 9, + /// CE_BINOP: [opcode, opval, opval] + CE_BINOP = 10, + /// CE_CAST: [opcode, opty, opval] + CE_CAST = 11, + /// CE_GEP: [n x operands] + CE_GEP_OLD = 12, + /// CE_SELECT: [opval, opval, opval] + CE_SELECT = 13, + /// CE_EXTRACTELT: [opty, opval, opval] + CE_EXTRACTELT = 14, + /// CE_INSERTELT: [opval, opval, opval] + CE_INSERTELT = 15, + /// CE_SHUFFLEVEC: [opval, opval, opval] + CE_SHUFFLEVEC = 16, + /// CE_CMP: [opty, opval, opval, pred] + CE_CMP = 17, + /// INLINEASM: [sideeffect|alignstack,asmstr,conststr] + INLINEASM_OLD = 18, + /// SHUFVEC_EX: [opty, opval, opval, opval] + CE_SHUFVEC_EX = 19, + /// INBOUNDS_GEP: [n x operands] + CE_INBOUNDS_GEP = 20, + /// BLOCKADDRESS: [fnty, fnval, bb#] + BLOCKADDRESS = 21, + /// DATA: [n x elements] + DATA = 22, + /// INLINEASM: [sideeffect|alignstack|asmdialect,asmstr,conststr] + INLINEASM_OLD2 = 23, + /// [opty, flags, n x operands] + CE_GEP_WITH_INRANGE_INDEX_OLD = 24, + /// CE_UNOP: [opcode, opval] + CE_UNOP = 25, + /// POISON + POISON = 26, + /// DSO_LOCAL_EQUIVALENT [gvty, gv] + DSO_LOCAL_EQUIVALENT = 27, + /// INLINEASM: [sideeffect|alignstack|asmdialect|unwind,asmstr, + /// conststr] + INLINEASM_OLD3 = 28, + /// NO_CFI [ fty, f ] + NO_CFI_VALUE = 29, + /// INLINEASM: [fnty,sideeffect|alignstack|asmdialect|unwind, + /// asmstr,conststr] + INLINEASM = 30, + /// [opty, flags, range, n x operands] + CE_GEP_WITH_INRANGE = 31, + /// [opty, flags, n x operands] + CE_GEP = 32, + /// [ptr, key, disc, addrdisc] + PTRAUTH = 33, + }; + + pub const SetType = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.SETTYPE) }, + .{ .fixed_runtime = Builder.Type }, + }; + type_id: Builder.Type, + }; + + pub const Null = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.NULL) }, + }; + }; + + pub const Undef = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.UNDEF) }, + }; + }; + + pub const Poison = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.POISON) }, + }; + }; + + pub const Integer = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.INTEGER) }, + .{ .vbr = 16 }, + }; + value: u64, + }; + + pub const Half = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.FLOAT) }, + .{ .fixed = 16 }, + }; + value: u16, + }; + + pub const Float = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.FLOAT) }, + .{ .fixed = 32 }, + }; + value: u32, + }; + + pub const Double = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.FLOAT) }, + .{ .vbr = 6 }, + }; + value: u64, + }; + + pub const Fp80 = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.FLOAT) }, + .{ .vbr = 6 }, + .{ .vbr = 6 }, + }; + hi: u64, + lo: u16, + }; + + pub const Fp128 = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.FLOAT) }, + .{ .vbr = 6 }, + .{ .vbr = 6 }, + }; + lo: u64, + hi: u64, + }; + + pub const Aggregate = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.AGGREGATE) }, + .{ .array_fixed = 32 }, + }; + values: []const Builder.Constant, + }; + + pub const String = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.STRING) }, + .{ .array_fixed = 8 }, + }; + string: []const u8, + }; + + pub const CString = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.CSTRING) }, + .{ .array_fixed = 8 }, + }; + string: []const u8, + }; + + pub const Cast = struct { + const CastOpcode = Builder.CastOpcode; + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.CE_CAST) }, + .{ .fixed = @bitSizeOf(CastOpcode) }, + .{ .fixed_runtime = Builder.Type }, + ConstantAbbrev, + }; + + opcode: CastOpcode, + type_index: Builder.Type, + val: Builder.Constant, + }; + + pub const Binary = struct { + const BinaryOpcode = Builder.BinaryOpcode; + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.CE_BINOP) }, + .{ .fixed = @bitSizeOf(BinaryOpcode) }, + ConstantAbbrev, + ConstantAbbrev, + }; + + opcode: BinaryOpcode, + lhs: Builder.Constant, + rhs: Builder.Constant, + }; + + pub const Cmp = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.CE_CMP) }, + .{ .fixed_runtime = Builder.Type }, + ConstantAbbrev, + ConstantAbbrev, + .{ .vbr = 6 }, + }; + + ty: Builder.Type, + lhs: Builder.Constant, + rhs: Builder.Constant, + pred: u32, + }; + + pub const ExtractElement = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.CE_EXTRACTELT) }, + .{ .fixed_runtime = Builder.Type }, + ConstantAbbrev, + .{ .fixed_runtime = Builder.Type }, + ConstantAbbrev, + }; + + val_type: Builder.Type, + val: Builder.Constant, + index_type: Builder.Type, + index: Builder.Constant, + }; + + pub const InsertElement = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.CE_INSERTELT) }, + ConstantAbbrev, + ConstantAbbrev, + .{ .fixed_runtime = Builder.Type }, + ConstantAbbrev, + }; + + val: Builder.Constant, + elem: Builder.Constant, + index_type: Builder.Type, + index: Builder.Constant, + }; + + pub const ShuffleVector = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.CE_SHUFFLEVEC) }, + ValueAbbrev, + ValueAbbrev, + ValueAbbrev, + }; + + lhs: Builder.Constant, + rhs: Builder.Constant, + mask: Builder.Constant, + }; + + pub const ShuffleVectorEx = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.CE_SHUFVEC_EX) }, + .{ .fixed_runtime = Builder.Type }, + ValueAbbrev, + ValueAbbrev, + ValueAbbrev, + }; + + ty: Builder.Type, + lhs: Builder.Constant, + rhs: Builder.Constant, + mask: Builder.Constant, + }; + + pub const BlockAddress = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.ConstantsBlock.Code.BLOCKADDRESS) }, + .{ .fixed_runtime = Builder.Type }, + ConstantAbbrev, + BlockAbbrev, + }; + type_id: Builder.Type, + function: u32, + block: u32, + }; + + pub const DsoLocalEquivalentOrNoCfi = struct { + pub const ops = [_]AbbrevOp{ + .{ .fixed = 5 }, + .{ .fixed_runtime = Builder.Type }, + ConstantAbbrev, + }; + code: ModuleBlock.ConstantsBlock.Code, + type_id: Builder.Type, + function: u32, + }; + }; + + /// The function body block (FUNCTION_BLOCK_ID) describes function bodies. It + /// can contain a constant block (CONSTANTS_BLOCK_ID). + pub const FunctionBlock = struct { + pub const id: BlockId = .FUNCTION; + + pub const abbrevs = [_]type{ + ModuleBlock.FunctionBlock.DeclareBlocks, + ModuleBlock.FunctionBlock.Call, + ModuleBlock.FunctionBlock.CallFast, + ModuleBlock.FunctionBlock.FNeg, + ModuleBlock.FunctionBlock.FNegFast, + ModuleBlock.FunctionBlock.Binary, + ModuleBlock.FunctionBlock.BinaryNoWrap, + ModuleBlock.FunctionBlock.BinaryExact, + ModuleBlock.FunctionBlock.BinaryFast, + ModuleBlock.FunctionBlock.Cmp, + ModuleBlock.FunctionBlock.CmpFast, + ModuleBlock.FunctionBlock.Select, + ModuleBlock.FunctionBlock.SelectFast, + ModuleBlock.FunctionBlock.Cast, + ModuleBlock.FunctionBlock.Alloca, + ModuleBlock.FunctionBlock.GetElementPtr, + ModuleBlock.FunctionBlock.ExtractValue, + ModuleBlock.FunctionBlock.InsertValue, + ModuleBlock.FunctionBlock.ExtractElement, + ModuleBlock.FunctionBlock.InsertElement, + ModuleBlock.FunctionBlock.ShuffleVector, + ModuleBlock.FunctionBlock.RetVoid, + ModuleBlock.FunctionBlock.Ret, + ModuleBlock.FunctionBlock.Unreachable, + ModuleBlock.FunctionBlock.Load, + ModuleBlock.FunctionBlock.LoadAtomic, + ModuleBlock.FunctionBlock.Store, + ModuleBlock.FunctionBlock.StoreAtomic, + ModuleBlock.FunctionBlock.BrUnconditional, + ModuleBlock.FunctionBlock.BrConditional, + ModuleBlock.FunctionBlock.VaArg, + ModuleBlock.FunctionBlock.AtomicRmw, + ModuleBlock.FunctionBlock.CmpXchg, + ModuleBlock.FunctionBlock.Fence, + ModuleBlock.FunctionBlock.DebugLoc, + ModuleBlock.FunctionBlock.DebugLocAgain, + ModuleBlock.FunctionBlock.ColdOperandBundle, + ModuleBlock.FunctionBlock.IndirectBr, + }; + + pub const Code = enum(u7) { + /// DECLAREBLOCKS: [n] + DECLAREBLOCKS = 1, + + /// BINOP: [opcode, ty, opval, opval] + INST_BINOP = 2, + /// CAST: [opcode, ty, opty, opval] + INST_CAST = 3, + /// GEP: [n x operands] + INST_GEP_OLD = 4, + /// SELECT: [ty, opval, opval, opval] + INST_SELECT = 5, + /// EXTRACTELT: [opty, opval, opval] + INST_EXTRACTELT = 6, + /// INSERTELT: [ty, opval, opval, opval] + INST_INSERTELT = 7, + /// SHUFFLEVEC: [ty, opval, opval, opval] + INST_SHUFFLEVEC = 8, + /// CMP: [opty, opval, opval, pred] + INST_CMP = 9, + + /// RET: [opty,opval<both optional>] + INST_RET = 10, + /// BR: [bb#, bb#, cond] or [bb#] + INST_BR = 11, + /// SWITCH: [opty, op0, op1, ...] + INST_SWITCH = 12, + /// INVOKE: [attr, fnty, op0,op1, ...] + INST_INVOKE = 13, + /// UNREACHABLE + INST_UNREACHABLE = 15, + + /// PHI: [ty, val0,bb0, ...] + INST_PHI = 16, + /// ALLOCA: [instty, opty, op, align] + INST_ALLOCA = 19, + /// LOAD: [opty, op, align, vol] + INST_LOAD = 20, + /// VAARG: [valistty, valist, instty] + /// This store code encodes the pointer type, rather than the value type + /// this is so information only available in the pointer type (e.g. address + /// spaces) is retained. + INST_VAARG = 23, + /// STORE: [ptrty,ptr,val, align, vol] + INST_STORE_OLD = 24, + + /// EXTRACTVAL: [n x operands] + INST_EXTRACTVAL = 26, + /// INSERTVAL: [n x operands] + INST_INSERTVAL = 27, + /// fcmp/icmp returning Int1TY or vector of Int1Ty. Same as CMP, exists to + /// support legacy vicmp/vfcmp instructions. + /// CMP2: [opty, opval, opval, pred] + INST_CMP2 = 28, + /// new select on i1 or [N x i1] + /// VSELECT: [ty,opval,opval,predty,pred] + INST_VSELECT = 29, + /// INBOUNDS_GEP: [n x operands] + INST_INBOUNDS_GEP_OLD = 30, + /// INDIRECTBR: [opty, op0, op1, ...] + INST_INDIRECTBR = 31, + + /// DEBUG_LOC_AGAIN + DEBUG_LOC_AGAIN = 33, + + /// CALL: [attr, cc, fnty, fnid, args...] + INST_CALL = 34, + + /// DEBUG_LOC: [Line,Col,ScopeVal, IAVal] + DEBUG_LOC = 35, + /// FENCE: [ordering, synchscope] + INST_FENCE = 36, + /// CMPXCHG: [ptrty, ptr, cmp, val, vol, + /// ordering, synchscope, + /// failure_ordering?, weak?] + INST_CMPXCHG_OLD = 37, + /// ATOMICRMW: [ptrty,ptr,val, operation, + /// align, vol, + /// ordering, synchscope] + INST_ATOMICRMW_OLD = 38, + /// RESUME: [opval] + INST_RESUME = 39, + /// LANDINGPAD: [ty,val,val,num,id0,val0...] + INST_LANDINGPAD_OLD = 40, + /// LOAD: [opty, op, align, vol, + /// ordering, synchscope] + INST_LOADATOMIC = 41, + /// STORE: [ptrty,ptr,val, align, vol + /// ordering, synchscope] + INST_STOREATOMIC_OLD = 42, + + /// GEP: [inbounds, n x operands] + INST_GEP = 43, + /// STORE: [ptrty,ptr,valty,val, align, vol] + INST_STORE = 44, + /// STORE: [ptrty,ptr,val, align, vol + INST_STOREATOMIC = 45, + /// CMPXCHG: [ptrty, ptr, cmp, val, vol, + /// success_ordering, synchscope, + /// failure_ordering, weak] + INST_CMPXCHG = 46, + /// LANDINGPAD: [ty,val,num,id0,val0...] + INST_LANDINGPAD = 47, + /// CLEANUPRET: [val] or [val,bb#] + INST_CLEANUPRET = 48, + /// CATCHRET: [val,bb#] + INST_CATCHRET = 49, + /// CATCHPAD: [bb#,bb#,num,args...] + INST_CATCHPAD = 50, + /// CLEANUPPAD: [num,args...] + INST_CLEANUPPAD = 51, + /// CATCHSWITCH: [num,args...] or [num,args...,bb] + INST_CATCHSWITCH = 52, + /// OPERAND_BUNDLE: [tag#, value...] + OPERAND_BUNDLE = 55, + /// UNOP: [opcode, ty, opval] + INST_UNOP = 56, + /// CALLBR: [attr, cc, norm, transfs, + /// fnty, fnid, args...] + INST_CALLBR = 57, + /// FREEZE: [opty, opval] + INST_FREEZE = 58, + /// ATOMICRMW: [ptrty, ptr, valty, val, + /// operation, align, vol, + /// ordering, synchscope] + INST_ATOMICRMW = 59, + /// BLOCKADDR_USERS: [value...] + BLOCKADDR_USERS = 60, + + /// [DILocation, DILocalVariable, DIExpression, ValueAsMetadata] + DEBUG_RECORD_VALUE = 61, + /// [DILocation, DILocalVariable, DIExpression, ValueAsMetadata] + DEBUG_RECORD_DECLARE = 62, + /// [DILocation, DILocalVariable, DIExpression, ValueAsMetadata, + /// DIAssignID, DIExpression (addr), ValueAsMetadata (addr)] + DEBUG_RECORD_ASSIGN = 63, + /// [DILocation, DILocalVariable, DIExpression, Value] + DEBUG_RECORD_VALUE_SIMPLE = 64, + /// [DILocation, DILabel] + DEBUG_RECORD_LABEL = 65, + }; + + pub const DeclareBlocks = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.DECLAREBLOCKS) }, + .{ .vbr = 8 }, + }; + num_blocks: usize, + }; + + pub const Call = struct { + pub const CallType = packed struct(u17) { + tail: bool = false, + call_conv: Builder.CallConv, + reserved: u3 = 0, + must_tail: bool = false, + // We always use the explicit type version as that is what LLVM does + explicit_type: bool = true, + no_tail: bool = false, + }; + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_CALL) }, + .{ .fixed_runtime = Builder.FunctionAttributes }, + .{ .fixed = @bitSizeOf(CallType) }, + .{ .fixed_runtime = Builder.Type }, + ValueAbbrev, // Callee + ValueArrayAbbrev, // Args + }; + + attributes: Builder.FunctionAttributes, + call_type: CallType, + type_id: Builder.Type, + callee: Builder.Value, + args: []const Builder.Value, + }; + + pub const CallFast = struct { + const CallType = packed struct(u18) { + tail: bool = false, + call_conv: Builder.CallConv, + reserved: u3 = 0, + must_tail: bool = false, + // We always use the explicit type version as that is what LLVM does + explicit_type: bool = true, + no_tail: bool = false, + fast: bool = true, + }; + + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_CALL) }, + .{ .fixed_runtime = Builder.FunctionAttributes }, + .{ .fixed = @bitSizeOf(CallType) }, + .{ .fixed = @bitSizeOf(Builder.FastMath) }, + .{ .fixed_runtime = Builder.Type }, + ValueAbbrev, // Callee + ValueArrayAbbrev, // Args + }; + + attributes: Builder.FunctionAttributes, + call_type: CallType, + fast_math: Builder.FastMath, + type_id: Builder.Type, + callee: Builder.Value, + args: []const Builder.Value, + }; + + pub const FNeg = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_UNOP) }, + ValueAbbrev, + .{ .literal = 0 }, + }; + + val: u32, + }; + + pub const FNegFast = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_UNOP) }, + ValueAbbrev, + .{ .literal = 0 }, + .{ .fixed = @bitSizeOf(Builder.FastMath) }, + }; + + val: u32, + fast_math: Builder.FastMath, + }; + + pub const Binary = struct { + const BinaryOpcode = Builder.BinaryOpcode; + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_BINOP) }, + ValueAbbrev, + ValueAbbrev, + .{ .fixed = @bitSizeOf(BinaryOpcode) }, + }; + + lhs: u32, + rhs: u32, + opcode: BinaryOpcode, + }; + + pub const BinaryNoWrap = struct { + const BinaryOpcode = Builder.BinaryOpcode; + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_BINOP) }, + ValueAbbrev, + ValueAbbrev, + .{ .fixed = @bitSizeOf(BinaryOpcode) }, + .{ .fixed = 2 }, + }; + + lhs: u32, + rhs: u32, + opcode: BinaryOpcode, + flags: packed struct(u2) { + no_unsigned_wrap: bool, + no_signed_wrap: bool, + }, + }; + + pub const BinaryExact = struct { + const BinaryOpcode = Builder.BinaryOpcode; + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_BINOP) }, + ValueAbbrev, + ValueAbbrev, + .{ .fixed = @bitSizeOf(BinaryOpcode) }, + .{ .literal = 1 }, + }; + + lhs: u32, + rhs: u32, + opcode: BinaryOpcode, + }; + + pub const BinaryFast = struct { + const BinaryOpcode = Builder.BinaryOpcode; + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_BINOP) }, + ValueAbbrev, + ValueAbbrev, + .{ .fixed = @bitSizeOf(BinaryOpcode) }, + .{ .fixed = @bitSizeOf(Builder.FastMath) }, + }; + + lhs: u32, + rhs: u32, + opcode: BinaryOpcode, + fast_math: Builder.FastMath, + }; + + pub const Cmp = struct { + const CmpPredicate = Builder.CmpPredicate; + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_CMP2) }, + ValueAbbrev, + ValueAbbrev, + .{ .fixed = @bitSizeOf(CmpPredicate) }, + }; + + lhs: u32, + rhs: u32, + pred: CmpPredicate, + }; + + pub const CmpFast = struct { + const CmpPredicate = Builder.CmpPredicate; + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_CMP2) }, + ValueAbbrev, + ValueAbbrev, + .{ .fixed = @bitSizeOf(CmpPredicate) }, + .{ .fixed = @bitSizeOf(Builder.FastMath) }, + }; + + lhs: u32, + rhs: u32, + pred: CmpPredicate, + fast_math: Builder.FastMath, + }; + + pub const Select = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_VSELECT) }, + ValueAbbrev, + ValueAbbrev, + ValueAbbrev, + }; + + lhs: u32, + rhs: u32, + cond: u32, + }; + + pub const SelectFast = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_VSELECT) }, + ValueAbbrev, + ValueAbbrev, + ValueAbbrev, + .{ .fixed = @bitSizeOf(Builder.FastMath) }, + }; + + lhs: u32, + rhs: u32, + cond: u32, + fast_math: Builder.FastMath, + }; + + pub const Cast = struct { + const CastOpcode = Builder.CastOpcode; + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_CAST) }, + ValueAbbrev, + .{ .fixed_runtime = Builder.Type }, + .{ .fixed = @bitSizeOf(CastOpcode) }, + }; + + val: u32, + type_index: Builder.Type, + opcode: CastOpcode, + }; + + pub const Alloca = struct { + pub const Flags = packed struct(u11) { + align_lower: u5, + inalloca: bool, + explicit_type: bool, + swift_error: bool, + align_upper: u3, + }; + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_ALLOCA) }, + .{ .fixed_runtime = Builder.Type }, + .{ .fixed_runtime = Builder.Type }, + ValueAbbrev, + .{ .fixed = @bitSizeOf(Flags) }, + }; + + inst_type: Builder.Type, + len_type: Builder.Type, + len_value: u32, + flags: Flags, + }; + + pub const RetVoid = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_RET) }, + }; + }; + + pub const Ret = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_RET) }, + ValueAbbrev, + }; + val: u32, + }; + + pub const GetElementPtr = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_GEP) }, + .{ .fixed = 1 }, + .{ .fixed_runtime = Builder.Type }, + ValueAbbrev, + ValueArrayAbbrev, + }; + + is_inbounds: bool, + type_index: Builder.Type, + base: Builder.Value, + indices: []const Builder.Value, + }; + + pub const ExtractValue = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_EXTRACTVAL) }, + ValueAbbrev, + ValueArrayAbbrev, + }; + + val: u32, + indices: []const u32, + }; + + pub const InsertValue = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_INSERTVAL) }, + ValueAbbrev, + ValueAbbrev, + ValueArrayAbbrev, + }; + + val: u32, + elem: u32, + indices: []const u32, + }; + + pub const ExtractElement = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_EXTRACTELT) }, + ValueAbbrev, + ValueAbbrev, + }; + + val: u32, + index: u32, + }; + + pub const InsertElement = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_INSERTELT) }, + ValueAbbrev, + ValueAbbrev, + ValueAbbrev, + }; + + val: u32, + elem: u32, + index: u32, + }; + + pub const ShuffleVector = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_SHUFFLEVEC) }, + ValueAbbrev, + ValueAbbrev, + ValueAbbrev, + }; + + lhs: u32, + rhs: u32, + mask: u32, + }; + + pub const Unreachable = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_UNREACHABLE) }, + }; + }; + + pub const Load = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_LOAD) }, + ValueAbbrev, + .{ .fixed_runtime = Builder.Type }, + .{ .fixed = @bitSizeOf(Builder.Alignment) }, + .{ .fixed = 1 }, + }; + ptr: u32, + ty: Builder.Type, + alignment: std.meta.Int(.unsigned, @bitSizeOf(Builder.Alignment)), + is_volatile: bool, + }; + + pub const LoadAtomic = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_LOADATOMIC) }, + ValueAbbrev, + .{ .fixed_runtime = Builder.Type }, + .{ .fixed = @bitSizeOf(Builder.Alignment) }, + .{ .fixed = 1 }, + .{ .fixed = @bitSizeOf(Builder.AtomicOrdering) }, + .{ .fixed = @bitSizeOf(Builder.SyncScope) }, + }; + ptr: u32, + ty: Builder.Type, + alignment: std.meta.Int(.unsigned, @bitSizeOf(Builder.Alignment)), + is_volatile: bool, + success_ordering: Builder.AtomicOrdering, + sync_scope: Builder.SyncScope, + }; + + pub const Store = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_STORE) }, + ValueAbbrev, + ValueAbbrev, + .{ .fixed = @bitSizeOf(Builder.Alignment) }, + .{ .fixed = 1 }, + }; + ptr: u32, + val: u32, + alignment: std.meta.Int(.unsigned, @bitSizeOf(Builder.Alignment)), + is_volatile: bool, + }; + + pub const StoreAtomic = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_STOREATOMIC) }, + ValueAbbrev, + ValueAbbrev, + .{ .fixed = @bitSizeOf(Builder.Alignment) }, + .{ .fixed = 1 }, + .{ .fixed = @bitSizeOf(Builder.AtomicOrdering) }, + .{ .fixed = @bitSizeOf(Builder.SyncScope) }, + }; + ptr: u32, + val: u32, + alignment: std.meta.Int(.unsigned, @bitSizeOf(Builder.Alignment)), + is_volatile: bool, + success_ordering: Builder.AtomicOrdering, + sync_scope: Builder.SyncScope, + }; + + pub const BrUnconditional = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_BR) }, + BlockAbbrev, + }; + block: u32, + }; + + pub const BrConditional = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_BR) }, + BlockAbbrev, + BlockAbbrev, + BlockAbbrev, + }; + then_block: u32, + else_block: u32, + condition: u32, + }; + + pub const VaArg = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_VAARG) }, + .{ .fixed_runtime = Builder.Type }, + ValueAbbrev, + .{ .fixed_runtime = Builder.Type }, + }; + list_type: Builder.Type, + list: u32, + type: Builder.Type, + }; + + pub const AtomicRmw = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_ATOMICRMW) }, + ValueAbbrev, + ValueAbbrev, + .{ .fixed = @bitSizeOf(Builder.Function.Instruction.AtomicRmw.Operation) }, + .{ .fixed = 1 }, + .{ .fixed = @bitSizeOf(Builder.AtomicOrdering) }, + .{ .fixed = @bitSizeOf(Builder.SyncScope) }, + .{ .fixed = @bitSizeOf(Builder.Alignment) }, + }; + ptr: u32, + val: u32, + operation: Builder.Function.Instruction.AtomicRmw.Operation, + is_volatile: bool, + success_ordering: Builder.AtomicOrdering, + sync_scope: Builder.SyncScope, + alignment: std.meta.Int(.unsigned, @bitSizeOf(Builder.Alignment)), + }; + + pub const CmpXchg = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_CMPXCHG) }, + ValueAbbrev, + ValueAbbrev, + ValueAbbrev, + .{ .fixed = 1 }, + .{ .fixed = @bitSizeOf(Builder.AtomicOrdering) }, + .{ .fixed = @bitSizeOf(Builder.SyncScope) }, + .{ .fixed = @bitSizeOf(Builder.AtomicOrdering) }, + .{ .fixed = 1 }, + .{ .fixed = @bitSizeOf(Builder.Alignment) }, + }; + ptr: u32, + cmp: u32, + new: u32, + is_volatile: bool, + success_ordering: Builder.AtomicOrdering, + sync_scope: Builder.SyncScope, + failure_ordering: Builder.AtomicOrdering, + is_weak: bool, + alignment: std.meta.Int(.unsigned, @bitSizeOf(Builder.Alignment)), + }; + + pub const Fence = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_FENCE) }, + .{ .fixed = @bitSizeOf(Builder.AtomicOrdering) }, + .{ .fixed = @bitSizeOf(Builder.SyncScope) }, + }; + ordering: Builder.AtomicOrdering, + sync_scope: Builder.SyncScope, + }; + + pub const DebugLoc = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.DEBUG_LOC) }, + LineAbbrev, + ColumnAbbrev, + MetadataAbbrev, + MetadataAbbrev, + .{ .literal = 0 }, + }; + line: u32, + column: u32, + scope: Builder.Metadata.Optional, + inlined_at: Builder.Metadata.Optional, + }; + + pub const DebugLocAgain = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.DEBUG_LOC_AGAIN) }, + }; + }; + + pub const ColdOperandBundle = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.OPERAND_BUNDLE) }, + .{ .literal = 0 }, + }; + }; + + pub const IndirectBr = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.Code.INST_INDIRECTBR) }, + .{ .fixed_runtime = Builder.Type }, + ValueAbbrev, + BlockArrayAbbrev, + }; + ty: Builder.Type, + addr: Builder.Value, + targets: []const Builder.Function.Block.Index, + }; + + pub const ValueSymtabBlock = struct { + pub const id: BlockId = .VALUE_SYMTAB; + + pub const abbrevs = [_]type{ + ModuleBlock.FunctionBlock.ValueSymtabBlock.BlockEntry, + }; + + /// Value symbol table codes. + pub const Code = enum(u3) { + /// VST_ENTRY: [valueid, namechar x N] + ENTRY = 1, + /// VST_BBENTRY: [bbid, namechar x N] + BBENTRY = 2, + /// VST_FNENTRY: [valueid, offset, namechar x N] + FNENTRY = 3, + /// VST_COMBINED_ENTRY: [valueid, refguid] + COMBINED_ENTRY = 5, + }; + + pub const BlockEntry = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.FunctionBlock.ValueSymtabBlock.Code.BBENTRY) }, + ValueAbbrev, + .{ .array_fixed = 8 }, + }; + value_id: u32, + string: []const u8, + }; + }; + + pub const MetadataBlock = struct { + pub const id: BlockId = .METADATA; + + pub const abbrevs = [_]type{ + ModuleBlock.FunctionBlock.MetadataBlock.Value, + }; + + pub const Value = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.VALUE) }, + .{ .fixed = 32 }, // variable + .{ .fixed = 32 }, // expression + }; + + ty: Builder.Type, + value: Builder.Value, + }; + }; + + pub const MetadataAttachmentBlock = struct { + pub const id: BlockId = .METADATA_ATTACHMENT; + + pub const abbrevs = [_]type{ + ModuleBlock.FunctionBlock.MetadataAttachmentBlock.AttachmentGlobalSingle, + ModuleBlock.FunctionBlock.MetadataAttachmentBlock.AttachmentInstructionSingle, + }; + + pub const AttachmentGlobalSingle = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.ATTACHMENT) }, + .{ .fixed = 1 }, + MetadataAbbrev, + }; + kind: FixedMetadataKind, + metadata: Builder.Metadata, + }; + + pub const AttachmentInstructionSingle = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.ATTACHMENT) }, + ValueAbbrev, + .{ .fixed = 5 }, + MetadataAbbrev, + }; + inst: u32, + kind: FixedMetadataKind, + metadata: Builder.Metadata, + }; + }; + }; + + pub const MetadataBlock = struct { + pub const id: BlockId = .METADATA; + + pub const abbrevs = [_]type{ + ModuleBlock.MetadataBlock.Strings, + ModuleBlock.MetadataBlock.File, + ModuleBlock.MetadataBlock.CompileUnit, + ModuleBlock.MetadataBlock.Subprogram, + ModuleBlock.MetadataBlock.LexicalBlock, + ModuleBlock.MetadataBlock.Location, + ModuleBlock.MetadataBlock.BasicType, + ModuleBlock.MetadataBlock.CompositeType, + ModuleBlock.MetadataBlock.DerivedType, + ModuleBlock.MetadataBlock.SubroutineType, + ModuleBlock.MetadataBlock.Enumerator, + ModuleBlock.MetadataBlock.Subrange, + ModuleBlock.MetadataBlock.Expression, + ModuleBlock.MetadataBlock.Node, + ModuleBlock.MetadataBlock.LocalVar, + ModuleBlock.MetadataBlock.Parameter, + ModuleBlock.MetadataBlock.GlobalVar, + ModuleBlock.MetadataBlock.GlobalVarExpression, + ModuleBlock.MetadataBlock.Constant, + ModuleBlock.MetadataBlock.Name, + ModuleBlock.MetadataBlock.NamedNode, + ModuleBlock.MetadataBlock.GlobalDeclAttachment, + }; + + pub const Code = enum(u6) { + /// MDSTRING: [values] + STRING_OLD = 1, + /// VALUE: [type num, value num] + VALUE = 2, + /// NODE: [n x md num] + NODE = 3, + /// STRING: [values] + NAME = 4, + /// DISTINCT_NODE: [n x md num] + DISTINCT_NODE = 5, + /// [n x [id, name]] + KIND = 6, + /// [distinct, line, col, scope, inlined-at?] + LOCATION = 7, + /// OLD_NODE: [n x (type num, value num)] + OLD_NODE = 8, + /// OLD_FN_NODE: [n x (type num, value num)] + OLD_FN_NODE = 9, + /// NAMED_NODE: [n x mdnodes] + NAMED_NODE = 10, + /// [m x [value, [n x [id, mdnode]]] + ATTACHMENT = 11, + /// [distinct, tag, vers, header, n x md num] + GENERIC_DEBUG = 12, + /// [distinct, count, lo] + SUBRANGE = 13, + /// [isUnsigned|distinct, value, name] + ENUMERATOR = 14, + /// [distinct, tag, name, size, align, enc] + BASIC_TYPE = 15, + /// [distinct, filename, directory, checksumkind, checksum] + FILE = 16, + /// [distinct, ...] + DERIVED_TYPE = 17, + /// [distinct, ...] + COMPOSITE_TYPE = 18, + /// [distinct, flags, types, cc] + SUBROUTINE_TYPE = 19, + /// [distinct, ...] + COMPILE_UNIT = 20, + /// [distinct, ...] + SUBPROGRAM = 21, + /// [distinct, scope, file, line, column] + LEXICAL_BLOCK = 22, + ///[distinct, scope, file, discriminator] + LEXICAL_BLOCK_FILE = 23, + /// [distinct, scope, file, name, line, exportSymbols] + NAMESPACE = 24, + /// [distinct, scope, name, type, ...] + TEMPLATE_TYPE = 25, + /// [distinct, scope, name, type, value, ...] + TEMPLATE_VALUE = 26, + /// [distinct, ...] + GLOBAL_VAR = 27, + /// [distinct, ...] + LOCAL_VAR = 28, + /// [distinct, n x element] + EXPRESSION = 29, + /// [distinct, name, file, line, ...] + OBJC_PROPERTY = 30, + /// [distinct, tag, scope, entity, line, name] + IMPORTED_ENTITY = 31, + /// [distinct, scope, name, ...] + MODULE = 32, + /// [distinct, macinfo, line, name, value] + MACRO = 33, + /// [distinct, macinfo, line, file, ...] + MACRO_FILE = 34, + /// [count, offset] blob([lengths][chars]) + STRINGS = 35, + /// [valueid, n x [id, mdnode]] + GLOBAL_DECL_ATTACHMENT = 36, + /// [distinct, var, expr] + GLOBAL_VAR_EXPR = 37, + /// [offset] + INDEX_OFFSET = 38, + /// [bitpos] + INDEX = 39, + /// [distinct, scope, name, file, line] + LABEL = 40, + /// [distinct, name, size, align,...] + STRING_TYPE = 41, + /// [distinct, scope, name, variable,...] + COMMON_BLOCK = 44, + /// [distinct, count, lo, up, stride] + GENERIC_SUBRANGE = 45, + /// [n x [type num, value num]] + ARG_LIST = 46, + /// [distinct, ...] + ASSIGN_ID = 47, + }; + + pub const Strings = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.STRINGS) }, + .{ .vbr = 6 }, + .{ .vbr = 6 }, + .blob, + }; + num_strings: u32, + strings_offset: u32, + blob: []const u8, + }; + + pub const File = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.FILE) }, + .{ .literal = 0 }, // is distinct + MetadataAbbrev, // filename + MetadataAbbrev, // directory + .{ .literal = 0 }, // checksum + .{ .literal = 0 }, // checksum + }; + + filename: Builder.Metadata.String.Optional, + directory: Builder.Metadata.String.Optional, + }; + + pub const CompileUnit = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.COMPILE_UNIT) }, + .{ .literal = 1 }, // is distinct + .{ .literal = std.dwarf.LANG.C99 }, // source language + MetadataAbbrev, // file + MetadataAbbrev, // producer + .{ .fixed = 1 }, // isOptimized + .{ .literal = 0 }, // raw flags + .{ .literal = 0 }, // runtime version + .{ .literal = 0 }, // split debug file name + .{ .literal = 1 }, // emission kind + MetadataAbbrev, // enums + .{ .literal = 0 }, // retained types + .{ .literal = 0 }, // subprograms + MetadataAbbrev, // globals + .{ .literal = 0 }, // imported entities + .{ .literal = 0 }, // DWO ID + .{ .literal = 0 }, // macros + .{ .literal = 0 }, // split debug inlining + .{ .literal = 0 }, // debug info profiling + .{ .literal = 0 }, // name table kind + .{ .literal = 0 }, // ranges base address + .{ .literal = 0 }, // raw sysroot + .{ .literal = 0 }, // raw SDK + }; + + file: Builder.Metadata.Optional, + producer: Builder.Metadata.String.Optional, + is_optimized: bool, + enums: Builder.Metadata.Optional, + globals: Builder.Metadata.Optional, + }; + + pub const Subprogram = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.SUBPROGRAM) }, + .{ .literal = 0b111 }, // is distinct | has sp flags | has flags + MetadataAbbrev, // scope + MetadataAbbrev, // name + MetadataAbbrev, // linkage name + MetadataAbbrev, // file + LineAbbrev, // line + MetadataAbbrev, // type + LineAbbrev, // scope line + .{ .literal = 0 }, // containing type + .{ .fixed = 32 }, // sp flags + .{ .literal = 0 }, // virtual index + .{ .fixed = 32 }, // flags + MetadataAbbrev, // compile unit + .{ .literal = 0 }, // template params + .{ .literal = 0 }, // declaration + .{ .literal = 0 }, // retained nodes + .{ .literal = 0 }, // this adjustment + .{ .literal = 0 }, // thrown types + .{ .literal = 0 }, // annotations + .{ .literal = 0 }, // target function name + }; + + scope: Builder.Metadata.Optional, + name: Builder.Metadata.String.Optional, + linkage_name: Builder.Metadata.String.Optional, + file: Builder.Metadata.Optional, + line: u32, + ty: Builder.Metadata.Optional, + scope_line: u32, + sp_flags: Builder.Metadata.Subprogram.DISPFlags, + flags: Builder.Metadata.DIFlags, + compile_unit: Builder.Metadata.Optional, + }; + + pub const LexicalBlock = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.LEXICAL_BLOCK) }, + .{ .literal = 0 }, // is distinct + MetadataAbbrev, // scope + MetadataAbbrev, // file + LineAbbrev, // line + ColumnAbbrev, // column + }; + + scope: Builder.Metadata.Optional, + file: Builder.Metadata.Optional, + line: u32, + column: u32, + }; + + pub const Location = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.LOCATION) }, + .{ .literal = 0 }, // is distinct + LineAbbrev, // line + ColumnAbbrev, // column + MetadataAbbrev, // scope + MetadataAbbrev, // inlined at + .{ .literal = 0 }, // is implicit code + }; + + line: u32, + column: u32, + scope: Builder.Metadata, + inlined_at: Builder.Metadata.Optional, + }; + + pub const BasicType = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.BASIC_TYPE) }, + .{ .literal = 0 }, // is distinct + .{ .literal = std.dwarf.TAG.base_type }, // tag + MetadataAbbrev, // name + .{ .vbr = 6 }, // size in bits + .{ .literal = 0 }, // align in bits + .{ .vbr = 8 }, // encoding + .{ .literal = 0 }, // flags + }; + + name: Builder.Metadata.String.Optional, + size_in_bits: u64, + encoding: u32, + }; + + pub const CompositeType = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.COMPOSITE_TYPE) }, + .{ .literal = 0 | 0x2 }, // is distinct | is not used in old type ref + .{ .fixed = 32 }, // tag + MetadataAbbrev, // name + MetadataAbbrev, // file + LineAbbrev, // line + MetadataAbbrev, // scope + MetadataAbbrev, // underlying type + .{ .vbr = 6 }, // size in bits + .{ .vbr = 6 }, // align in bits + .{ .literal = 0 }, // offset in bits + .{ .fixed = 32 }, // flags + MetadataAbbrev, // elements + .{ .literal = 0 }, // runtime lang + .{ .literal = 0 }, // vtable holder + .{ .literal = 0 }, // template params + .{ .literal = 0 }, // raw id + .{ .literal = 0 }, // discriminator + .{ .literal = 0 }, // data location + .{ .literal = 0 }, // associated + .{ .literal = 0 }, // allocated + .{ .literal = 0 }, // rank + .{ .literal = 0 }, // annotations + }; + + tag: u32, + name: Builder.Metadata.String.Optional, + file: Builder.Metadata.Optional, + line: u32, + scope: Builder.Metadata.Optional, + underlying_type: Builder.Metadata.Optional, + size_in_bits: u64, + align_in_bits: u64, + flags: Builder.Metadata.DIFlags, + elements: Builder.Metadata.Optional, + }; + + pub const DerivedType = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.DERIVED_TYPE) }, + .{ .literal = 0 }, // is distinct + .{ .fixed = 32 }, // tag + MetadataAbbrev, // name + MetadataAbbrev, // file + LineAbbrev, // line + MetadataAbbrev, // scope + MetadataAbbrev, // underlying type + .{ .vbr = 6 }, // size in bits + .{ .vbr = 6 }, // align in bits + .{ .vbr = 6 }, // offset in bits + .{ .literal = 0 }, // flags + .{ .literal = 0 }, // extra data + }; + + tag: u32, + name: Builder.Metadata.String.Optional, + file: Builder.Metadata.Optional, + line: u32, + scope: Builder.Metadata.Optional, + underlying_type: Builder.Metadata.Optional, + size_in_bits: u64, + align_in_bits: u64, + offset_in_bits: u64, + }; + + pub const SubroutineType = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.SUBROUTINE_TYPE) }, + .{ .literal = 0 | 0x2 }, // is distinct | has no old type refs + .{ .literal = 0 }, // flags + MetadataAbbrev, // types + .{ .literal = 0 }, // cc + }; + + types: Builder.Metadata.Optional, + }; + + pub const Enumerator = struct { + pub const Flags = packed struct(u3) { + distinct: bool = false, + unsigned: bool, + bigint: bool = true, + }; + + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.ENUMERATOR) }, + .{ .fixed = @bitSizeOf(Flags) }, // flags + .{ .vbr = 6 }, // bit width + MetadataAbbrev, // name + .{ .vbr = 16 }, // integer value + }; + + flags: Flags, + bit_width: u32, + name: Builder.Metadata.String.Optional, + value: u64, + }; + + pub const Subrange = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.SUBRANGE) }, + .{ .literal = 0 | (2 << 1) }, // is distinct | version + MetadataAbbrev, // count + MetadataAbbrev, // lower bound + .{ .literal = 0 }, // upper bound + .{ .literal = 0 }, // stride + }; + + count: Builder.Metadata.Optional, + lower_bound: Builder.Metadata.Optional, + }; + + pub const Expression = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.EXPRESSION) }, + .{ .literal = 0 | (3 << 1) }, // is distinct | version + MetadataArrayAbbrev, // elements + }; + + elements: []const u32, + }; + + pub const Node = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.NODE) }, + MetadataArrayAbbrev, // elements + }; + + elements: []const Builder.Metadata.Optional, + }; + + pub const LocalVar = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.LOCAL_VAR) }, + .{ .literal = 0b10 }, // is distinct | has alignment + MetadataAbbrev, // scope + MetadataAbbrev, // name + MetadataAbbrev, // file + LineAbbrev, // line + MetadataAbbrev, // type + .{ .literal = 0 }, // arg + .{ .literal = 0 }, // flags + .{ .literal = 0 }, // align bits + .{ .literal = 0 }, // annotations + }; + + scope: Builder.Metadata.Optional, + name: Builder.Metadata.String.Optional, + file: Builder.Metadata.Optional, + line: u32, + ty: Builder.Metadata.Optional, + }; + + pub const Parameter = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.LOCAL_VAR) }, + .{ .literal = 0b10 }, // is distinct | has alignment + MetadataAbbrev, // scope + MetadataAbbrev, // name + MetadataAbbrev, // file + LineAbbrev, // line + MetadataAbbrev, // type + .{ .vbr = 4 }, // arg + .{ .literal = 0 }, // flags + .{ .literal = 0 }, // align bits + .{ .literal = 0 }, // annotations + }; + + scope: Builder.Metadata.Optional, + name: Builder.Metadata.String.Optional, + file: Builder.Metadata.Optional, + line: u32, + ty: Builder.Metadata.Optional, + arg: u32, + }; + + pub const GlobalVar = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.GLOBAL_VAR) }, + .{ .literal = 0b101 }, // is distinct | version + MetadataAbbrev, // scope + MetadataAbbrev, // name + MetadataAbbrev, // linkage name + MetadataAbbrev, // file + LineAbbrev, // line + MetadataAbbrev, // type + .{ .fixed = 1 }, // local + .{ .literal = 1 }, // defined + .{ .literal = 0 }, // static data members declaration + .{ .literal = 0 }, // template params + .{ .literal = 0 }, // align in bits + .{ .literal = 0 }, // annotations + }; + + scope: Builder.Metadata.Optional, + name: Builder.Metadata.String.Optional, + linkage_name: Builder.Metadata.String.Optional, + file: Builder.Metadata.Optional, + line: u32, + ty: Builder.Metadata.Optional, + local: bool, + }; + + pub const GlobalVarExpression = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.GLOBAL_VAR_EXPR) }, + .{ .literal = 0 }, // is distinct + MetadataAbbrev, // variable + MetadataAbbrev, // expression + }; + + variable: Builder.Metadata.Optional, + expression: Builder.Metadata.Optional, + }; + + pub const Constant = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.VALUE) }, + MetadataAbbrev, // type + MetadataAbbrev, // value + }; + + ty: Builder.Type, + constant: Builder.Constant, + }; + + pub const Name = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.NAME) }, + .{ .array_fixed = 8 }, // name + }; + + name: []const u8, + }; + + pub const NamedNode = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.NAMED_NODE) }, + MetadataArrayAbbrev, // elements + }; + + elements: []const Builder.Metadata, + }; + + pub const GlobalDeclAttachment = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.GLOBAL_DECL_ATTACHMENT) }, + ValueAbbrev, // value id + .{ .fixed = 1 }, // kind + MetadataAbbrev, // elements + }; + + value: Builder.Constant, + kind: FixedMetadataKind, + metadata: Builder.Metadata, + }; + }; + + /// TYPE blocks have codes for each type primitive they use. + pub const TypeBlock = struct { + pub const id: BlockId = .TYPE; + + pub const abbrevs = [_]type{ + ModuleBlock.TypeBlock.NumEntry, + ModuleBlock.TypeBlock.Simple, + ModuleBlock.TypeBlock.Opaque, + ModuleBlock.TypeBlock.Integer, + ModuleBlock.TypeBlock.StructAnon, + ModuleBlock.TypeBlock.StructNamed, + ModuleBlock.TypeBlock.StructName, + ModuleBlock.TypeBlock.Array, + ModuleBlock.TypeBlock.Vector, + ModuleBlock.TypeBlock.Pointer, + ModuleBlock.TypeBlock.Target, + ModuleBlock.TypeBlock.Function, + }; + + pub const Code = enum(u5) { + /// NUMENTRY: [numentries] + NUMENTRY = 1, + + // Type Codes + /// VOID + VOID = 2, + /// FLOAT + FLOAT = 3, + /// DOUBLE + DOUBLE = 4, + /// LABEL + LABEL = 5, + /// OPAQUE + OPAQUE = 6, + /// INTEGER: [width] + INTEGER = 7, + /// POINTER: [pointee type] + POINTER = 8, + + /// FUNCTION: [vararg, attrid, retty, paramty x N] + FUNCTION_OLD = 9, + + /// HALF + HALF = 10, + + /// ARRAY: [numelts, eltty] + ARRAY = 11, + /// VECTOR: [numelts, eltty] + VECTOR = 12, + + // These are not with the other floating point types because they're + // a late addition, and putting them in the right place breaks + // binary compatibility. + /// X86 LONG DOUBLE + X86_FP80 = 13, + /// LONG DOUBLE (112 bit mantissa) + FP128 = 14, + /// PPC LONG DOUBLE (2 doubles) + PPC_FP128 = 15, + + /// METADATA + METADATA = 16, + + /// X86 MMX + X86_MMX = 17, + + /// STRUCT_ANON: [ispacked, eltty x N] + STRUCT_ANON = 18, + /// STRUCT_NAME: [strchr x N] + STRUCT_NAME = 19, + /// STRUCT_NAMED: [ispacked, eltty x N] + STRUCT_NAMED = 20, + + /// FUNCTION: [vararg, retty, paramty x N] + FUNCTION = 21, + + /// TOKEN + TOKEN = 22, + + /// BRAIN FLOATING POINT + BFLOAT = 23, + /// X86 AMX + X86_AMX = 24, + + /// OPAQUE_POINTER: [addrspace] + OPAQUE_POINTER = 25, + + /// TARGET_TYPE + TARGET_TYPE = 26, + }; + + pub const NumEntry = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.TypeBlock.Code.NUMENTRY) }, + .{ .fixed = 32 }, + }; + num: u32, + }; + + pub const Simple = struct { + pub const ops = [_]AbbrevOp{ + .{ .vbr = 4 }, + }; + code: ModuleBlock.TypeBlock.Code, + }; + + pub const Opaque = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.TypeBlock.Code.OPAQUE) }, + .{ .literal = 0 }, + }; + }; + + pub const Integer = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.TypeBlock.Code.INTEGER) }, + .{ .fixed = 28 }, + }; + width: u28, + }; + + pub const StructAnon = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.TypeBlock.Code.STRUCT_ANON) }, + .{ .fixed = 1 }, + .{ .array_fixed_runtime = Builder.Type }, + }; + is_packed: bool, + types: []const Builder.Type, + }; + + pub const StructNamed = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.TypeBlock.Code.STRUCT_NAMED) }, + .{ .fixed = 1 }, + .{ .array_fixed_runtime = Builder.Type }, + }; + is_packed: bool, + types: []const Builder.Type, + }; + + pub const StructName = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.TypeBlock.Code.STRUCT_NAME) }, + .{ .array_fixed = 8 }, + }; + string: []const u8, + }; + + pub const Array = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.TypeBlock.Code.ARRAY) }, + .{ .vbr = 16 }, + .{ .fixed_runtime = Builder.Type }, + }; + len: u64, + child: Builder.Type, + }; + + pub const Vector = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.TypeBlock.Code.VECTOR) }, + .{ .vbr = 16 }, + .{ .fixed_runtime = Builder.Type }, + }; + len: u64, + child: Builder.Type, + }; + + pub const Pointer = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.TypeBlock.Code.OPAQUE_POINTER) }, + .{ .vbr = 4 }, + }; + addr_space: Builder.AddrSpace, }; - width: u28, - }; - pub const StructAnon = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 18 }, - .{ .fixed = 1 }, - .{ .array_fixed_runtime = Builder.Type }, + pub const Target = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.TypeBlock.Code.TARGET_TYPE) }, + .{ .vbr = 4 }, + .{ .array_fixed_runtime = Builder.Type }, + .{ .array_fixed = 32 }, + }; + num_types: u32, + types: []const Builder.Type, + ints: []const u32, }; - is_packed: bool, - types: []const Builder.Type, - }; - pub const StructNamed = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 20 }, - .{ .fixed = 1 }, - .{ .array_fixed_runtime = Builder.Type }, + pub const Function = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.TypeBlock.Code.FUNCTION) }, + .{ .fixed = 1 }, + .{ .fixed_runtime = Builder.Type }, + .{ .array_fixed_runtime = Builder.Type }, + }; + is_vararg: bool, + return_type: Builder.Type, + param_types: []const Builder.Type, }; - is_packed: bool, - types: []const Builder.Type, }; - pub const StructName = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 19 }, - .{ .array_fixed = 8 }, - }; - string: []const u8, - }; + pub const OperandBundleTagsBlock = struct { + pub const id: BlockId = .OPERAND_BUNDLE_TAGS; - pub const Array = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 11 }, - .{ .vbr = 16 }, - .{ .fixed_runtime = Builder.Type }, + pub const abbrevs = [_]type{ + ModuleBlock.OperandBundleTagsBlock.OperandBundleTag, }; - len: u64, - child: Builder.Type, - }; - pub const Vector = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 12 }, - .{ .vbr = 16 }, - .{ .fixed_runtime = Builder.Type }, + pub const Code = enum(u1) { + /// TAG: [strchr x N] + OPERAND_BUNDLE_TAG = 1, }; - len: u64, - child: Builder.Type, - }; - pub const Pointer = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 25 }, - .{ .vbr = 4 }, + pub const OperandBundleTag = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.OperandBundleTagsBlock.Code.OPERAND_BUNDLE_TAG) }, + .array_char6, + }; + tag: []const u8, }; - addr_space: Builder.AddrSpace, }; - pub const Target = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 26 }, - .{ .vbr = 4 }, - .{ .array_fixed_runtime = Builder.Type }, - .{ .array_fixed = 32 }, - }; - num_types: u32, - types: []const Builder.Type, - ints: []const u32, - }; + pub const MetadataKindBlock = struct { + pub const id: BlockId = .METADATA_KIND; - pub const Function = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 21 }, - .{ .fixed = 1 }, - .{ .fixed_runtime = Builder.Type }, - .{ .array_fixed_runtime = Builder.Type }, + pub const abbrevs = [_]type{ + ModuleBlock.MetadataKindBlock.Kind, }; - is_vararg: bool, - return_type: Builder.Type, - param_types: []const Builder.Type, - }; -}; - -pub const Paramattr = struct { - pub const id = 9; - - pub const abbrevs = [_]type{ - Entry, - }; - pub const Entry = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 2 }, - .{ .array_vbr = 8 }, + pub const Kind = struct { + pub const ops = [_]AbbrevOp{ + .{ .literal = @intFromEnum(ModuleBlock.MetadataBlock.Code.KIND) }, + .{ .vbr = 4 }, + .{ .array_fixed = 8 }, + }; + id: u32, + name: []const u8, }; - group_indices: []const u64, }; }; -pub const ParamattrGroup = struct { - pub const id = 10; - - pub const abbrevs = [_]type{}; -}; - -pub const Constants = struct { - pub const id = 11; +/// Identification block contains a string that describes the producer details, +/// and an epoch that defines the auto-upgrade capability. +pub const IdentificationBlock = struct { + pub const id: BlockId = .IDENTIFICATION; pub const abbrevs = [_]type{ - SetType, - Null, - Undef, - Poison, - Integer, - Half, - Float, - Double, - Fp80, - Fp128, - Aggregate, - String, - CString, - Cast, - Binary, - Cmp, - ExtractElement, - InsertElement, - ShuffleVector, - ShuffleVectorEx, - BlockAddress, - DsoLocalEquivalentOrNoCfi, - }; - - pub const SetType = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 1 }, - .{ .fixed_runtime = Builder.Type }, - }; - type_id: Builder.Type, - }; - - pub const Null = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 2 }, - }; - }; - - pub const Undef = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 3 }, - }; - }; - - pub const Poison = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 26 }, - }; - }; - - pub const Integer = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 4 }, - .{ .vbr = 16 }, - }; - value: u64, - }; - - pub const Half = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 6 }, - .{ .fixed = 16 }, - }; - value: u16, - }; - - pub const Float = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 6 }, - .{ .fixed = 32 }, - }; - value: u32, + IdentificationBlock.Version, + IdentificationBlock.Epoch, }; - pub const Double = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 6 }, - .{ .vbr = 6 }, - }; - value: u64, + pub const Code = enum(u2) { + /// IDENTIFICATION: [strchr x N] + STRING = 1, + /// EPOCH: [epoch#] + EPOCH = 2, }; - pub const Fp80 = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 6 }, - .{ .vbr = 6 }, - .{ .vbr = 6 }, - }; - hi: u64, - lo: u16, - }; - - pub const Fp128 = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 6 }, - .{ .vbr = 6 }, - .{ .vbr = 6 }, - }; - lo: u64, - hi: u64, - }; - - pub const Aggregate = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 7 }, - .{ .array_fixed = 32 }, - }; - values: []const Builder.Constant, - }; - - pub const String = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 8 }, - .{ .array_fixed = 8 }, - }; - string: []const u8, - }; - - pub const CString = struct { + pub const Version = struct { pub const ops = [_]AbbrevOp{ - .{ .literal = 9 }, + .{ .literal = @intFromEnum(IdentificationBlock.Code.STRING) }, .{ .array_fixed = 8 }, }; string: []const u8, }; - pub const Cast = struct { - const CastOpcode = Builder.CastOpcode; - pub const ops = [_]AbbrevOp{ - .{ .literal = 11 }, - .{ .fixed = @bitSizeOf(CastOpcode) }, - .{ .fixed_runtime = Builder.Type }, - ConstantAbbrev, - }; - - opcode: CastOpcode, - type_index: Builder.Type, - val: Builder.Constant, - }; - - pub const Binary = struct { - const BinaryOpcode = Builder.BinaryOpcode; - pub const ops = [_]AbbrevOp{ - .{ .literal = 10 }, - .{ .fixed = @bitSizeOf(BinaryOpcode) }, - ConstantAbbrev, - ConstantAbbrev, - }; - - opcode: BinaryOpcode, - lhs: Builder.Constant, - rhs: Builder.Constant, - }; - - pub const Cmp = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 17 }, - .{ .fixed_runtime = Builder.Type }, - ConstantAbbrev, - ConstantAbbrev, - .{ .vbr = 6 }, - }; - - ty: Builder.Type, - lhs: Builder.Constant, - rhs: Builder.Constant, - pred: u32, - }; - - pub const ExtractElement = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 14 }, - .{ .fixed_runtime = Builder.Type }, - ConstantAbbrev, - .{ .fixed_runtime = Builder.Type }, - ConstantAbbrev, - }; - - val_type: Builder.Type, - val: Builder.Constant, - index_type: Builder.Type, - index: Builder.Constant, - }; - - pub const InsertElement = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 15 }, - ConstantAbbrev, - ConstantAbbrev, - .{ .fixed_runtime = Builder.Type }, - ConstantAbbrev, - }; - - val: Builder.Constant, - elem: Builder.Constant, - index_type: Builder.Type, - index: Builder.Constant, - }; - - pub const ShuffleVector = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 16 }, - ValueAbbrev, - ValueAbbrev, - ValueAbbrev, - }; - - lhs: Builder.Constant, - rhs: Builder.Constant, - mask: Builder.Constant, - }; - - pub const ShuffleVectorEx = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 19 }, - .{ .fixed_runtime = Builder.Type }, - ValueAbbrev, - ValueAbbrev, - ValueAbbrev, - }; - - ty: Builder.Type, - lhs: Builder.Constant, - rhs: Builder.Constant, - mask: Builder.Constant, - }; - - pub const BlockAddress = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 21 }, - .{ .fixed_runtime = Builder.Type }, - ConstantAbbrev, - BlockAbbrev, - }; - type_id: Builder.Type, - function: u32, - block: u32, - }; - - pub const DsoLocalEquivalentOrNoCfi = struct { - pub const ops = [_]AbbrevOp{ - .{ .fixed = 5 }, - .{ .fixed_runtime = Builder.Type }, - ConstantAbbrev, - }; - code: u5, - type_id: Builder.Type, - function: u32, - }; -}; - -pub const MetadataKindBlock = struct { - pub const id = 22; - - pub const abbrevs = [_]type{ - Kind, - }; - - pub const Kind = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 6 }, - .{ .vbr = 4 }, - .{ .array_fixed = 8 }, - }; - id: u32, - name: []const u8, - }; -}; - -pub const MetadataAttachmentBlock = struct { - pub const id = 16; - - pub const abbrevs = [_]type{ - AttachmentGlobalSingle, - AttachmentInstructionSingle, - }; - - pub const AttachmentGlobalSingle = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.ATTACHMENT) }, - .{ .fixed = 1 }, - MetadataAbbrev, - }; - kind: FixedMetadataKind, - metadata: Builder.Metadata, - }; - - pub const AttachmentInstructionSingle = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.ATTACHMENT) }, - ValueAbbrev, - .{ .fixed = 5 }, - MetadataAbbrev, - }; - inst: u32, - kind: FixedMetadataKind, - metadata: Builder.Metadata, - }; -}; - -pub const MetadataBlock = struct { - pub const id = 15; - - pub const abbrevs = [_]type{ - Strings, - File, - CompileUnit, - Subprogram, - LexicalBlock, - Location, - BasicType, - CompositeType, - DerivedType, - SubroutineType, - Enumerator, - Subrange, - Expression, - Node, - LocalVar, - Parameter, - GlobalVar, - GlobalVarExpression, - Constant, - Name, - NamedNode, - GlobalDeclAttachment, - }; - - pub const Strings = struct { + pub const Epoch = struct { pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.STRINGS) }, + .{ .literal = @intFromEnum(IdentificationBlock.Code.EPOCH) }, .{ .vbr = 6 }, - .{ .vbr = 6 }, - .blob, - }; - num_strings: u32, - strings_offset: u32, - blob: []const u8, - }; - - pub const File = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.FILE) }, - .{ .literal = 0 }, // is distinct - MetadataAbbrev, // filename - MetadataAbbrev, // directory - .{ .literal = 0 }, // checksum - .{ .literal = 0 }, // checksum - }; - - filename: Builder.MetadataString, - directory: Builder.MetadataString, - }; - - pub const CompileUnit = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.COMPILE_UNIT) }, - .{ .literal = 1 }, // is distinct - .{ .literal = std.dwarf.LANG.C99 }, // source language - MetadataAbbrev, // file - MetadataAbbrev, // producer - .{ .fixed = 1 }, // isOptimized - .{ .literal = 0 }, // raw flags - .{ .literal = 0 }, // runtime version - .{ .literal = 0 }, // split debug file name - .{ .literal = 1 }, // emission kind - MetadataAbbrev, // enums - .{ .literal = 0 }, // retained types - .{ .literal = 0 }, // subprograms - MetadataAbbrev, // globals - .{ .literal = 0 }, // imported entities - .{ .literal = 0 }, // DWO ID - .{ .literal = 0 }, // macros - .{ .literal = 0 }, // split debug inlining - .{ .literal = 0 }, // debug info profiling - .{ .literal = 0 }, // name table kind - .{ .literal = 0 }, // ranges base address - .{ .literal = 0 }, // raw sysroot - .{ .literal = 0 }, // raw SDK - }; - - file: Builder.Metadata, - producer: Builder.MetadataString, - is_optimized: bool, - enums: Builder.Metadata, - globals: Builder.Metadata, - }; - - pub const Subprogram = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.SUBPROGRAM) }, - .{ .literal = 0b111 }, // is distinct | has sp flags | has flags - MetadataAbbrev, // scope - MetadataAbbrev, // name - MetadataAbbrev, // linkage name - MetadataAbbrev, // file - LineAbbrev, // line - MetadataAbbrev, // type - LineAbbrev, // scope line - .{ .literal = 0 }, // containing type - .{ .fixed = 32 }, // sp flags - .{ .literal = 0 }, // virtual index - .{ .fixed = 32 }, // flags - MetadataAbbrev, // compile unit - .{ .literal = 0 }, // template params - .{ .literal = 0 }, // declaration - .{ .literal = 0 }, // retained nodes - .{ .literal = 0 }, // this adjustment - .{ .literal = 0 }, // thrown types - .{ .literal = 0 }, // annotations - .{ .literal = 0 }, // target function name - }; - - scope: Builder.Metadata, - name: Builder.MetadataString, - linkage_name: Builder.MetadataString, - file: Builder.Metadata, - line: u32, - ty: Builder.Metadata, - scope_line: u32, - sp_flags: Builder.Metadata.Subprogram.DISPFlags, - flags: Builder.Metadata.DIFlags, - compile_unit: Builder.Metadata, - }; - - pub const LexicalBlock = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.LEXICAL_BLOCK) }, - .{ .literal = 0 }, // is distinct - MetadataAbbrev, // scope - MetadataAbbrev, // file - LineAbbrev, // line - ColumnAbbrev, // column - }; - - scope: Builder.Metadata, - file: Builder.Metadata, - line: u32, - column: u32, - }; - - pub const Location = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.LOCATION) }, - .{ .literal = 0 }, // is distinct - LineAbbrev, // line - ColumnAbbrev, // column - MetadataAbbrev, // scope - MetadataAbbrev, // inlined at - .{ .literal = 0 }, // is implicit code - }; - - line: u32, - column: u32, - scope: u32, - inlined_at: Builder.Metadata, - }; - - pub const BasicType = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.BASIC_TYPE) }, - .{ .literal = 0 }, // is distinct - .{ .literal = std.dwarf.TAG.base_type }, // tag - MetadataAbbrev, // name - .{ .vbr = 6 }, // size in bits - .{ .literal = 0 }, // align in bits - .{ .vbr = 8 }, // encoding - .{ .literal = 0 }, // flags - }; - - name: Builder.MetadataString, - size_in_bits: u64, - encoding: u32, - }; - - pub const CompositeType = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.COMPOSITE_TYPE) }, - .{ .literal = 0 | 0x2 }, // is distinct | is not used in old type ref - .{ .fixed = 32 }, // tag - MetadataAbbrev, // name - MetadataAbbrev, // file - LineAbbrev, // line - MetadataAbbrev, // scope - MetadataAbbrev, // underlying type - .{ .vbr = 6 }, // size in bits - .{ .vbr = 6 }, // align in bits - .{ .literal = 0 }, // offset in bits - .{ .fixed = 32 }, // flags - MetadataAbbrev, // elements - .{ .literal = 0 }, // runtime lang - .{ .literal = 0 }, // vtable holder - .{ .literal = 0 }, // template params - .{ .literal = 0 }, // raw id - .{ .literal = 0 }, // discriminator - .{ .literal = 0 }, // data location - .{ .literal = 0 }, // associated - .{ .literal = 0 }, // allocated - .{ .literal = 0 }, // rank - .{ .literal = 0 }, // annotations - }; - - tag: u32, - name: Builder.MetadataString, - file: Builder.Metadata, - line: u32, - scope: Builder.Metadata, - underlying_type: Builder.Metadata, - size_in_bits: u64, - align_in_bits: u64, - flags: Builder.Metadata.DIFlags, - elements: Builder.Metadata, - }; - - pub const DerivedType = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.DERIVED_TYPE) }, - .{ .literal = 0 }, // is distinct - .{ .fixed = 32 }, // tag - MetadataAbbrev, // name - MetadataAbbrev, // file - LineAbbrev, // line - MetadataAbbrev, // scope - MetadataAbbrev, // underlying type - .{ .vbr = 6 }, // size in bits - .{ .vbr = 6 }, // align in bits - .{ .vbr = 6 }, // offset in bits - .{ .literal = 0 }, // flags - .{ .literal = 0 }, // extra data - }; - - tag: u32, - name: Builder.MetadataString, - file: Builder.Metadata, - line: u32, - scope: Builder.Metadata, - underlying_type: Builder.Metadata, - size_in_bits: u64, - align_in_bits: u64, - offset_in_bits: u64, - }; - - pub const SubroutineType = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.SUBROUTINE_TYPE) }, - .{ .literal = 0 | 0x2 }, // is distinct | has no old type refs - .{ .literal = 0 }, // flags - MetadataAbbrev, // types - .{ .literal = 0 }, // cc - }; - - types: Builder.Metadata, - }; - - pub const Enumerator = struct { - pub const id: MetadataCode = .ENUMERATOR; - - pub const Flags = packed struct(u3) { - distinct: bool = false, - unsigned: bool, - bigint: bool = true, - }; - - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(Enumerator.id) }, - .{ .fixed = @bitSizeOf(Flags) }, // flags - .{ .vbr = 6 }, // bit width - MetadataAbbrev, // name - .{ .vbr = 16 }, // integer value }; - - flags: Flags, - bit_width: u32, - name: Builder.MetadataString, - value: u64, - }; - - pub const Subrange = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.SUBRANGE) }, - .{ .literal = 0 | (2 << 1) }, // is distinct | version - MetadataAbbrev, // count - MetadataAbbrev, // lower bound - .{ .literal = 0 }, // upper bound - .{ .literal = 0 }, // stride - }; - - count: Builder.Metadata, - lower_bound: Builder.Metadata, - }; - - pub const Expression = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.EXPRESSION) }, - .{ .literal = 0 | (3 << 1) }, // is distinct | version - MetadataArrayAbbrev, // elements - }; - - elements: []const u32, - }; - - pub const Node = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.NODE) }, - MetadataArrayAbbrev, // elements - }; - - elements: []const Builder.Metadata, - }; - - pub const LocalVar = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.LOCAL_VAR) }, - .{ .literal = 0b10 }, // is distinct | has alignment - MetadataAbbrev, // scope - MetadataAbbrev, // name - MetadataAbbrev, // file - LineAbbrev, // line - MetadataAbbrev, // type - .{ .literal = 0 }, // arg - .{ .literal = 0 }, // flags - .{ .literal = 0 }, // align bits - .{ .literal = 0 }, // annotations - }; - - scope: Builder.Metadata, - name: Builder.MetadataString, - file: Builder.Metadata, - line: u32, - ty: Builder.Metadata, - }; - - pub const Parameter = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.LOCAL_VAR) }, - .{ .literal = 0b10 }, // is distinct | has alignment - MetadataAbbrev, // scope - MetadataAbbrev, // name - MetadataAbbrev, // file - LineAbbrev, // line - MetadataAbbrev, // type - .{ .vbr = 4 }, // arg - .{ .literal = 0 }, // flags - .{ .literal = 0 }, // align bits - .{ .literal = 0 }, // annotations - }; - - scope: Builder.Metadata, - name: Builder.MetadataString, - file: Builder.Metadata, - line: u32, - ty: Builder.Metadata, - arg: u32, - }; - - pub const GlobalVar = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.GLOBAL_VAR) }, - .{ .literal = 0b101 }, // is distinct | version - MetadataAbbrev, // scope - MetadataAbbrev, // name - MetadataAbbrev, // linkage name - MetadataAbbrev, // file - LineAbbrev, // line - MetadataAbbrev, // type - .{ .fixed = 1 }, // local - .{ .literal = 1 }, // defined - .{ .literal = 0 }, // static data members declaration - .{ .literal = 0 }, // template params - .{ .literal = 0 }, // align in bits - .{ .literal = 0 }, // annotations - }; - - scope: Builder.Metadata, - name: Builder.MetadataString, - linkage_name: Builder.MetadataString, - file: Builder.Metadata, - line: u32, - ty: Builder.Metadata, - local: bool, - }; - - pub const GlobalVarExpression = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.GLOBAL_VAR_EXPR) }, - .{ .literal = 0 }, // is distinct - MetadataAbbrev, // variable - MetadataAbbrev, // expression - }; - - variable: Builder.Metadata, - expression: Builder.Metadata, - }; - - pub const Constant = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.VALUE) }, - MetadataAbbrev, // type - MetadataAbbrev, // value - }; - - ty: Builder.Type, - constant: Builder.Constant, - }; - - pub const Name = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.NAME) }, - .{ .array_fixed = 8 }, // name - }; - - name: []const u8, - }; - - pub const NamedNode = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.NAMED_NODE) }, - MetadataArrayAbbrev, // elements - }; - - elements: []const Builder.Metadata, - }; - - pub const GlobalDeclAttachment = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = @intFromEnum(MetadataCode.GLOBAL_DECL_ATTACHMENT) }, - ValueAbbrev, // value id - .{ .fixed = 1 }, // kind - MetadataAbbrev, // elements - }; - - value: Builder.Constant, - kind: FixedMetadataKind, - metadata: Builder.Metadata, - }; -}; - -pub const OperandBundleTags = struct { - pub const id = 21; - - pub const abbrevs = [_]type{OperandBundleTag}; - - pub const OperandBundleTag = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 1 }, - .array_char6, - }; - tag: []const u8, - }; -}; - -pub const FunctionMetadataBlock = struct { - pub const id = 15; - - pub const abbrevs = [_]type{ - Value, - }; - - pub const Value = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 2 }, - .{ .fixed = 32 }, // variable - .{ .fixed = 32 }, // expression - }; - - ty: Builder.Type, - value: Builder.Value, - }; -}; - -pub const FunctionBlock = struct { - pub const id = 12; - - pub const abbrevs = [_]type{ - DeclareBlocks, - Call, - CallFast, - FNeg, - FNegFast, - Binary, - BinaryNoWrap, - BinaryExact, - BinaryFast, - Cmp, - CmpFast, - Select, - SelectFast, - Cast, - Alloca, - GetElementPtr, - ExtractValue, - InsertValue, - ExtractElement, - InsertElement, - ShuffleVector, - RetVoid, - Ret, - Unreachable, - Load, - LoadAtomic, - Store, - StoreAtomic, - BrUnconditional, - BrConditional, - VaArg, - AtomicRmw, - CmpXchg, - Fence, - DebugLoc, - DebugLocAgain, - ColdOperandBundle, - IndirectBr, - }; - - pub const DeclareBlocks = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 1 }, - .{ .vbr = 8 }, - }; - num_blocks: usize, - }; - - pub const Call = struct { - pub const CallType = packed struct(u17) { - tail: bool = false, - call_conv: Builder.CallConv, - reserved: u3 = 0, - must_tail: bool = false, - // We always use the explicit type version as that is what LLVM does - explicit_type: bool = true, - no_tail: bool = false, - }; - pub const ops = [_]AbbrevOp{ - .{ .literal = 34 }, - .{ .fixed_runtime = Builder.FunctionAttributes }, - .{ .fixed = @bitSizeOf(CallType) }, - .{ .fixed_runtime = Builder.Type }, - ValueAbbrev, // Callee - ValueArrayAbbrev, // Args - }; - - attributes: Builder.FunctionAttributes, - call_type: CallType, - type_id: Builder.Type, - callee: Builder.Value, - args: []const Builder.Value, - }; - - pub const CallFast = struct { - const CallType = packed struct(u18) { - tail: bool = false, - call_conv: Builder.CallConv, - reserved: u3 = 0, - must_tail: bool = false, - // We always use the explicit type version as that is what LLVM does - explicit_type: bool = true, - no_tail: bool = false, - fast: bool = true, - }; - - pub const ops = [_]AbbrevOp{ - .{ .literal = 34 }, - .{ .fixed_runtime = Builder.FunctionAttributes }, - .{ .fixed = @bitSizeOf(CallType) }, - .{ .fixed = @bitSizeOf(Builder.FastMath) }, - .{ .fixed_runtime = Builder.Type }, - ValueAbbrev, // Callee - ValueArrayAbbrev, // Args - }; - - attributes: Builder.FunctionAttributes, - call_type: CallType, - fast_math: Builder.FastMath, - type_id: Builder.Type, - callee: Builder.Value, - args: []const Builder.Value, - }; - - pub const FNeg = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 56 }, - ValueAbbrev, - .{ .literal = 0 }, - }; - - val: u32, - }; - - pub const FNegFast = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 56 }, - ValueAbbrev, - .{ .literal = 0 }, - .{ .fixed = @bitSizeOf(Builder.FastMath) }, - }; - - val: u32, - fast_math: Builder.FastMath, - }; - - pub const Binary = struct { - const BinaryOpcode = Builder.BinaryOpcode; - pub const ops = [_]AbbrevOp{ - .{ .literal = 2 }, - ValueAbbrev, - ValueAbbrev, - .{ .fixed = @bitSizeOf(BinaryOpcode) }, - }; - - lhs: u32, - rhs: u32, - opcode: BinaryOpcode, - }; - - pub const BinaryNoWrap = struct { - const BinaryOpcode = Builder.BinaryOpcode; - pub const ops = [_]AbbrevOp{ - .{ .literal = 2 }, - ValueAbbrev, - ValueAbbrev, - .{ .fixed = @bitSizeOf(BinaryOpcode) }, - .{ .fixed = 2 }, - }; - - lhs: u32, - rhs: u32, - opcode: BinaryOpcode, - flags: packed struct(u2) { - no_unsigned_wrap: bool, - no_signed_wrap: bool, - }, - }; - - pub const BinaryExact = struct { - const BinaryOpcode = Builder.BinaryOpcode; - pub const ops = [_]AbbrevOp{ - .{ .literal = 2 }, - ValueAbbrev, - ValueAbbrev, - .{ .fixed = @bitSizeOf(BinaryOpcode) }, - .{ .literal = 1 }, - }; - - lhs: u32, - rhs: u32, - opcode: BinaryOpcode, - }; - - pub const BinaryFast = struct { - const BinaryOpcode = Builder.BinaryOpcode; - pub const ops = [_]AbbrevOp{ - .{ .literal = 2 }, - ValueAbbrev, - ValueAbbrev, - .{ .fixed = @bitSizeOf(BinaryOpcode) }, - .{ .fixed = @bitSizeOf(Builder.FastMath) }, - }; - - lhs: u32, - rhs: u32, - opcode: BinaryOpcode, - fast_math: Builder.FastMath, - }; - - pub const Cmp = struct { - const CmpPredicate = Builder.CmpPredicate; - pub const ops = [_]AbbrevOp{ - .{ .literal = 28 }, - ValueAbbrev, - ValueAbbrev, - .{ .fixed = @bitSizeOf(CmpPredicate) }, - }; - - lhs: u32, - rhs: u32, - pred: CmpPredicate, - }; - - pub const CmpFast = struct { - const CmpPredicate = Builder.CmpPredicate; - pub const ops = [_]AbbrevOp{ - .{ .literal = 28 }, - ValueAbbrev, - ValueAbbrev, - .{ .fixed = @bitSizeOf(CmpPredicate) }, - .{ .fixed = @bitSizeOf(Builder.FastMath) }, - }; - - lhs: u32, - rhs: u32, - pred: CmpPredicate, - fast_math: Builder.FastMath, - }; - - pub const Select = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 29 }, - ValueAbbrev, - ValueAbbrev, - ValueAbbrev, - }; - - lhs: u32, - rhs: u32, - cond: u32, - }; - - pub const SelectFast = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 29 }, - ValueAbbrev, - ValueAbbrev, - ValueAbbrev, - .{ .fixed = @bitSizeOf(Builder.FastMath) }, - }; - - lhs: u32, - rhs: u32, - cond: u32, - fast_math: Builder.FastMath, - }; - - pub const Cast = struct { - const CastOpcode = Builder.CastOpcode; - pub const ops = [_]AbbrevOp{ - .{ .literal = 3 }, - ValueAbbrev, - .{ .fixed_runtime = Builder.Type }, - .{ .fixed = @bitSizeOf(CastOpcode) }, - }; - - val: u32, - type_index: Builder.Type, - opcode: CastOpcode, - }; - - pub const Alloca = struct { - pub const Flags = packed struct(u11) { - align_lower: u5, - inalloca: bool, - explicit_type: bool, - swift_error: bool, - align_upper: u3, - }; - pub const ops = [_]AbbrevOp{ - .{ .literal = 19 }, - .{ .fixed_runtime = Builder.Type }, - .{ .fixed_runtime = Builder.Type }, - ValueAbbrev, - .{ .fixed = @bitSizeOf(Flags) }, - }; - - inst_type: Builder.Type, - len_type: Builder.Type, - len_value: u32, - flags: Flags, - }; - - pub const RetVoid = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 10 }, - }; - }; - - pub const Ret = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 10 }, - ValueAbbrev, - }; - val: u32, - }; - - pub const GetElementPtr = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 43 }, - .{ .fixed = 1 }, - .{ .fixed_runtime = Builder.Type }, - ValueAbbrev, - ValueArrayAbbrev, - }; - - is_inbounds: bool, - type_index: Builder.Type, - base: Builder.Value, - indices: []const Builder.Value, - }; - - pub const ExtractValue = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 26 }, - ValueAbbrev, - ValueArrayAbbrev, - }; - - val: u32, - indices: []const u32, - }; - - pub const InsertValue = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 27 }, - ValueAbbrev, - ValueAbbrev, - ValueArrayAbbrev, - }; - - val: u32, - elem: u32, - indices: []const u32, - }; - - pub const ExtractElement = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 6 }, - ValueAbbrev, - ValueAbbrev, - }; - - val: u32, - index: u32, - }; - - pub const InsertElement = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 7 }, - ValueAbbrev, - ValueAbbrev, - ValueAbbrev, - }; - - val: u32, - elem: u32, - index: u32, - }; - - pub const ShuffleVector = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 8 }, - ValueAbbrev, - ValueAbbrev, - ValueAbbrev, - }; - - lhs: u32, - rhs: u32, - mask: u32, - }; - - pub const Unreachable = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 15 }, - }; - }; - - pub const Load = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 20 }, - ValueAbbrev, - .{ .fixed_runtime = Builder.Type }, - .{ .fixed = @bitSizeOf(Builder.Alignment) }, - .{ .fixed = 1 }, - }; - ptr: u32, - ty: Builder.Type, - alignment: std.meta.Int(.unsigned, @bitSizeOf(Builder.Alignment)), - is_volatile: bool, - }; - - pub const LoadAtomic = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 41 }, - ValueAbbrev, - .{ .fixed_runtime = Builder.Type }, - .{ .fixed = @bitSizeOf(Builder.Alignment) }, - .{ .fixed = 1 }, - .{ .fixed = @bitSizeOf(Builder.AtomicOrdering) }, - .{ .fixed = @bitSizeOf(Builder.SyncScope) }, - }; - ptr: u32, - ty: Builder.Type, - alignment: std.meta.Int(.unsigned, @bitSizeOf(Builder.Alignment)), - is_volatile: bool, - success_ordering: Builder.AtomicOrdering, - sync_scope: Builder.SyncScope, - }; - - pub const Store = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 44 }, - ValueAbbrev, - ValueAbbrev, - .{ .fixed = @bitSizeOf(Builder.Alignment) }, - .{ .fixed = 1 }, - }; - ptr: u32, - val: u32, - alignment: std.meta.Int(.unsigned, @bitSizeOf(Builder.Alignment)), - is_volatile: bool, - }; - - pub const StoreAtomic = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 45 }, - ValueAbbrev, - ValueAbbrev, - .{ .fixed = @bitSizeOf(Builder.Alignment) }, - .{ .fixed = 1 }, - .{ .fixed = @bitSizeOf(Builder.AtomicOrdering) }, - .{ .fixed = @bitSizeOf(Builder.SyncScope) }, - }; - ptr: u32, - val: u32, - alignment: std.meta.Int(.unsigned, @bitSizeOf(Builder.Alignment)), - is_volatile: bool, - success_ordering: Builder.AtomicOrdering, - sync_scope: Builder.SyncScope, - }; - - pub const BrUnconditional = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 11 }, - BlockAbbrev, - }; - block: u32, - }; - - pub const BrConditional = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 11 }, - BlockAbbrev, - BlockAbbrev, - BlockAbbrev, - }; - then_block: u32, - else_block: u32, - condition: u32, - }; - - pub const VaArg = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 23 }, - .{ .fixed_runtime = Builder.Type }, - ValueAbbrev, - .{ .fixed_runtime = Builder.Type }, - }; - list_type: Builder.Type, - list: u32, - type: Builder.Type, - }; - - pub const AtomicRmw = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 59 }, - ValueAbbrev, - ValueAbbrev, - .{ .fixed = @bitSizeOf(Builder.Function.Instruction.AtomicRmw.Operation) }, - .{ .fixed = 1 }, - .{ .fixed = @bitSizeOf(Builder.AtomicOrdering) }, - .{ .fixed = @bitSizeOf(Builder.SyncScope) }, - .{ .fixed = @bitSizeOf(Builder.Alignment) }, - }; - ptr: u32, - val: u32, - operation: Builder.Function.Instruction.AtomicRmw.Operation, - is_volatile: bool, - success_ordering: Builder.AtomicOrdering, - sync_scope: Builder.SyncScope, - alignment: std.meta.Int(.unsigned, @bitSizeOf(Builder.Alignment)), - }; - - pub const CmpXchg = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 46 }, - ValueAbbrev, - ValueAbbrev, - ValueAbbrev, - .{ .fixed = 1 }, - .{ .fixed = @bitSizeOf(Builder.AtomicOrdering) }, - .{ .fixed = @bitSizeOf(Builder.SyncScope) }, - .{ .fixed = @bitSizeOf(Builder.AtomicOrdering) }, - .{ .fixed = 1 }, - .{ .fixed = @bitSizeOf(Builder.Alignment) }, - }; - ptr: u32, - cmp: u32, - new: u32, - is_volatile: bool, - success_ordering: Builder.AtomicOrdering, - sync_scope: Builder.SyncScope, - failure_ordering: Builder.AtomicOrdering, - is_weak: bool, - alignment: std.meta.Int(.unsigned, @bitSizeOf(Builder.Alignment)), - }; - - pub const Fence = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 36 }, - .{ .fixed = @bitSizeOf(Builder.AtomicOrdering) }, - .{ .fixed = @bitSizeOf(Builder.SyncScope) }, - }; - ordering: Builder.AtomicOrdering, - sync_scope: Builder.SyncScope, - }; - - pub const DebugLoc = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 35 }, - LineAbbrev, - ColumnAbbrev, - MetadataAbbrev, - MetadataAbbrev, - .{ .literal = 0 }, - }; - line: u32, - column: u32, - scope: Builder.Metadata, - inlined_at: Builder.Metadata, - }; - - pub const DebugLocAgain = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 33 }, - }; - }; - - pub const ColdOperandBundle = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 55 }, - .{ .literal = 0 }, - }; - }; - - pub const IndirectBr = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 31 }, - .{ .fixed_runtime = Builder.Type }, - ValueAbbrev, - BlockArrayAbbrev, - }; - ty: Builder.Type, - addr: Builder.Value, - targets: []const Builder.Function.Block.Index, + epoch: u32, }; }; -pub const FunctionValueSymbolTable = struct { - pub const id = 14; +pub const StrtabBlock = struct { + pub const id: BlockId = .STRTAB; - pub const abbrevs = [_]type{ - BlockEntry, - }; + pub const abbrevs = [_]type{Blob}; - pub const BlockEntry = struct { - pub const ops = [_]AbbrevOp{ - .{ .literal = 2 }, - ValueAbbrev, - .{ .array_fixed = 8 }, - }; - value_id: u32, - string: []const u8, + pub const Code = enum(u1) { + BLOB = 1, }; -}; - -pub const Strtab = struct { - pub const id = 23; - - pub const abbrevs = [_]type{Blob}; pub const Blob = struct { pub const ops = [_]AbbrevOp{ - .{ .literal = 1 }, + .{ .literal = @intFromEnum(StrtabBlock.Code.BLOB) }, .blob, }; blob: []const u8, diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 55f1fd14cb..e237cecd59 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -81,52 +81,67 @@ pub fn getExternalExecutor( // If the OS matches, we can use QEMU to emulate a foreign architecture. if (options.allow_qemu and os_match and (!cpu_ok or options.qemu_fixes_dl)) { return switch (candidate.cpu.arch) { - .aarch64 => Executor{ .qemu = "qemu-aarch64" }, - .aarch64_be => Executor{ .qemu = "qemu-aarch64_be" }, - .arm, .thumb => Executor{ .qemu = "qemu-arm" }, - .armeb, .thumbeb => Executor{ .qemu = "qemu-armeb" }, - .hexagon => Executor{ .qemu = "qemu-hexagon" }, - .loongarch64 => Executor{ .qemu = "qemu-loongarch64" }, - .m68k => Executor{ .qemu = "qemu-m68k" }, - .mips => Executor{ .qemu = "qemu-mips" }, - .mipsel => Executor{ .qemu = "qemu-mipsel" }, - .mips64 => Executor{ - .qemu = switch (candidate.abi) { - .gnuabin32, .muslabin32 => "qemu-mipsn32", - else => "qemu-mips64", - }, - }, - .mips64el => Executor{ - .qemu = switch (candidate.abi) { - .gnuabin32, .muslabin32 => "qemu-mipsn32el", - else => "qemu-mips64el", - }, - }, - .powerpc => Executor{ .qemu = "qemu-ppc" }, - .powerpc64 => Executor{ .qemu = "qemu-ppc64" }, - .powerpc64le => Executor{ .qemu = "qemu-ppc64le" }, - .riscv32 => Executor{ .qemu = "qemu-riscv32" }, - .riscv64 => Executor{ .qemu = "qemu-riscv64" }, - .s390x => Executor{ .qemu = "qemu-s390x" }, - .sparc => Executor{ - .qemu = if (candidate.cpu.has(.sparc, .v8plus)) - "qemu-sparc32plus" - else - "qemu-sparc", + inline .aarch64, + .arm, + .riscv64, + .x86, + .x86_64, + => |t| switch (candidate.os.tag) { + .linux, + .freebsd, + => .{ .qemu = switch (t) { + .x86 => "qemu-i386", + .x86_64 => switch (candidate.abi) { + .gnux32, .muslx32 => return bad_result, + else => "qemu-x86_64", + }, + else => "qemu-" ++ @tagName(t), + } }, + else => bad_result, }, - .sparc64 => Executor{ .qemu = "qemu-sparc64" }, - .x86 => Executor{ .qemu = "qemu-i386" }, - .x86_64 => switch (candidate.abi) { - .gnux32, .muslx32 => return bad_result, - else => Executor{ .qemu = "qemu-x86_64" }, + inline .aarch64_be, + .armeb, + .hexagon, + .loongarch64, + .m68k, + .mips, + .mipsel, + .mips64, + .mips64el, + .or1k, + .powerpc, + .powerpc64, + .powerpc64le, + .riscv32, + .s390x, + .sparc, + .sparc64, + .thumb, + .thumbeb, + .xtensa, + => |t| switch (candidate.os.tag) { + .linux, + => .{ .qemu = switch (t) { + .powerpc => "qemu-ppc", + .powerpc64 => "qemu-ppc64", + .powerpc64le => "qemu-ppc64le", + .mips64, .mips64el => switch (candidate.abi) { + .gnuabin32, .muslabin32 => if (t == .mips64el) "qemu-mipsn32el" else "qemu-mipsn32", + else => "qemu-" ++ @tagName(t), + }, + .sparc => if (candidate.cpu.has(.sparc, .v8plus)) "qemu-sparc32plus" else "qemu-sparc", + .thumb => "qemu-arm", + .thumbeb => "qemu-armeb", + else => "qemu-" ++ @tagName(t), + } }, + else => bad_result, }, - .xtensa => Executor{ .qemu = "qemu-xtensa" }, - else => return bad_result, + else => bad_result, }; } if (options.allow_wasmtime and candidate.cpu.arch.isWasm()) { - return Executor{ .wasmtime = "wasmtime" }; + return .{ .wasmtime = "wasmtime" }; } switch (candidate.os.tag) { @@ -142,7 +157,7 @@ pub fn getExternalExecutor( .x86_64 => host.cpu.arch == .x86_64, else => false, }; - return if (wine_supported) Executor{ .wine = "wine" } else bad_result; + return if (wine_supported) .{ .wine = "wine" } else bad_result; } return bad_result; }, @@ -154,7 +169,7 @@ pub fn getExternalExecutor( if (candidate.cpu.arch != host.cpu.arch) { return bad_result; } - return Executor{ .darling = "darling" }; + return .{ .darling = "darling" }; } return bad_result; }, diff --git a/lib/std/zig/system/linux.zig b/lib/std/zig/system/linux.zig index ac68791a16..bbe7f8eaf9 100644 --- a/lib/std/zig/system/linux.zig +++ b/lib/std/zig/system/linux.zig @@ -359,14 +359,11 @@ fn CpuinfoParser(comptime impl: anytype) type { return struct { fn parse(arch: Target.Cpu.Arch, reader: *std.Io.Reader) !?Target.Cpu { var obj: impl = .{}; - while (reader.takeDelimiterExclusive('\n')) |line| { + while (try reader.takeDelimiter('\n')) |line| { const colon_pos = mem.indexOfScalar(u8, line, ':') orelse continue; const key = mem.trimEnd(u8, line[0..colon_pos], " \t"); const value = mem.trimStart(u8, line[colon_pos + 1 ..], " \t"); if (!try obj.line_hook(key, value)) break; - } else |err| switch (err) { - error.EndOfStream => {}, - else => |e| return e, } return obj.finalize(arch); } |
