aboutsummaryrefslogtreecommitdiff
path: root/lib/std
diff options
context:
space:
mode:
Diffstat (limited to 'lib/std')
-rw-r--r--lib/std/Build.zig7
-rw-r--r--lib/std/Build/Step.zig55
-rw-r--r--lib/std/Build/Step/CheckObject.zig38
-rw-r--r--lib/std/Build/WebServer.zig14
-rw-r--r--lib/std/Io/Reader.zig140
-rw-r--r--lib/std/Io/Reader/Limited.zig44
-rw-r--r--lib/std/Io/Writer.zig37
-rw-r--r--lib/std/Target.zig16
-rw-r--r--lib/std/Thread.zig4
-rw-r--r--lib/std/array_hash_map.zig11
-rw-r--r--lib/std/array_list.zig30
-rw-r--r--lib/std/builtin.zig13
-rw-r--r--lib/std/c.zig332
-rw-r--r--lib/std/c/darwin.zig109
-rw-r--r--lib/std/c/dragonfly.zig40
-rw-r--r--lib/std/c/freebsd.zig2
-rw-r--r--lib/std/c/haiku.zig263
-rw-r--r--lib/std/c/openbsd.zig47
-rw-r--r--lib/std/c/solaris.zig23
-rw-r--r--lib/std/coff.zig1335
-rw-r--r--lib/std/compress/flate.zig29
-rw-r--r--lib/std/compress/flate/BlockWriter.zig591
-rw-r--r--lib/std/compress/flate/Compress.zig2740
-rw-r--r--lib/std/compress/flate/Decompress.zig391
-rw-r--r--lib/std/compress/flate/HuffmanEncoder.zig463
-rw-r--r--lib/std/compress/flate/Lookup.zig130
-rw-r--r--lib/std/compress/flate/Token.zig333
-rw-r--r--lib/std/compress/flate/token.zig286
-rw-r--r--lib/std/crypto.zig3
-rw-r--r--lib/std/crypto/25519/ed25519.zig2
-rw-r--r--lib/std/crypto/aes_ccm.zig876
-rw-r--r--lib/std/crypto/cbc_mac.zig152
-rw-r--r--lib/std/crypto/tls/Client.zig2
-rw-r--r--lib/std/debug.zig1534
-rw-r--r--lib/std/debug/Coverage.zig3
-rw-r--r--lib/std/debug/Dwarf.zig1542
-rw-r--r--lib/std/debug/Dwarf/SelfUnwinder.zig347
-rw-r--r--lib/std/debug/Dwarf/Unwind.zig702
-rw-r--r--lib/std/debug/Dwarf/Unwind/VirtualMachine.zig470
-rw-r--r--lib/std/debug/Dwarf/abi.zig351
-rw-r--r--lib/std/debug/Dwarf/call_frame.zig292
-rw-r--r--lib/std/debug/Dwarf/expression.zig158
-rw-r--r--lib/std/debug/ElfFile.zig536
-rw-r--r--lib/std/debug/Info.zig30
-rw-r--r--lib/std/debug/Pdb.zig37
-rw-r--r--lib/std/debug/SelfInfo.zig2238
-rw-r--r--lib/std/debug/SelfInfo/Elf.zig507
-rw-r--r--lib/std/debug/SelfInfo/MachO.zig983
-rw-r--r--lib/std/debug/SelfInfo/Windows.zig559
-rw-r--r--lib/std/debug/cpu_context.zig1888
-rw-r--r--lib/std/dwarf/EH.zig53
-rw-r--r--lib/std/dynamic_library.zig3
-rw-r--r--lib/std/elf.zig3
-rw-r--r--lib/std/fs.zig49
-rw-r--r--lib/std/fs/Dir.zig288
-rw-r--r--lib/std/fs/File.zig209
-rw-r--r--lib/std/fs/path.zig2
-rw-r--r--lib/std/fs/test.zig152
-rw-r--r--lib/std/heap.zig33
-rw-r--r--lib/std/heap/PageAllocator.zig2
-rw-r--r--lib/std/heap/debug_allocator.zig51
-rw-r--r--lib/std/http/test.zig2
-rw-r--r--lib/std/macho.zig119
-rw-r--r--lib/std/math/isnan.zig2
-rw-r--r--lib/std/mem.zig34
-rw-r--r--lib/std/net.zig5
-rw-r--r--lib/std/os.zig2
-rw-r--r--lib/std/os/emscripten.zig38
-rw-r--r--lib/std/os/freebsd.zig1
-rw-r--r--lib/std/os/linux.zig12
-rw-r--r--lib/std/os/linux/aarch64.zig22
-rw-r--r--lib/std/os/linux/arm.zig36
-rw-r--r--lib/std/os/linux/hexagon.zig32
-rw-r--r--lib/std/os/linux/loongarch64.zig42
-rw-r--r--lib/std/os/linux/m68k.zig27
-rw-r--r--lib/std/os/linux/mips.zig8
-rw-r--r--lib/std/os/linux/mips64.zig8
-rw-r--r--lib/std/os/linux/powerpc.zig32
-rw-r--r--lib/std/os/linux/powerpc64.zig42
-rw-r--r--lib/std/os/linux/riscv32.zig38
-rw-r--r--lib/std/os/linux/riscv64.zig38
-rw-r--r--lib/std/os/linux/s390x.zig41
-rw-r--r--lib/std/os/linux/sparc64.zig121
-rw-r--r--lib/std/os/linux/x86.zig148
-rw-r--r--lib/std/os/linux/x86_64.zig201
-rw-r--r--lib/std/os/windows.zig14
-rw-r--r--lib/std/posix.zig138
-rw-r--r--lib/std/process.zig34
-rw-r--r--lib/std/process/Child.zig2
-rw-r--r--lib/std/start.zig7
-rw-r--r--lib/std/std.zig16
-rw-r--r--lib/std/testing.zig60
-rw-r--r--lib/std/testing/FailingAllocator.zig8
-rw-r--r--lib/std/zig/ErrorBundle.zig12
-rw-r--r--lib/std/zig/Server.zig22
-rw-r--r--lib/std/zig/llvm/Builder.zig1717
-rw-r--r--lib/std/zig/llvm/bitcode_writer.zig62
-rw-r--r--lib/std/zig/llvm/ir.zig3660
-rw-r--r--lib/std/zig/system.zig99
-rw-r--r--lib/std/zig/system/linux.zig5
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(&regular_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(&regular_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, &sections) |*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(&section_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, &sections, 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, &sections, 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,
- &sections,
- 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, &sections, 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, &sections, 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(&section_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, &sections, 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(
- &section_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, &sections, 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(
+ &section_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(&section_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);
}