authorgravatar for carl@astholm.seCarl Åstholm <carl@astholm.se> 2026-05-23 22:48:11+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-26 20:37:09+02:00
logfc3406a96169ee72c33ba6a0c965ec62fbd44539
tree8af07a171ce2a5615d7600ed162c74ff64a770c8
parent456b2ec07a8c5fa4d34aeb89a8909dfc4c78cb77

std.debug.Pdb: deduplicate inline source locations

Previously, if the same inline function was called multiple times by the same caller, the inline function's frame would be repeated multiple times, once for each prior call.

4 files changed, 94 insertions(+), 63 deletions(-)

lib/std/debug/Pdb.zig+61-34
...@@ -26,10 +26,11 @@ pub const Module = struct {...@@ -26,10 +26,11 @@ pub const Module = struct {
26 symbols: []u8,26 symbols: []u8,
27 subsect_info: []u8,27 subsect_info: []u8,
28 checksum_offset: ?usize,28 checksum_offset: ?usize,
29 /// The inlinee source lines, sorted by inlinee. This saves us from repeatedly doing linear29 /// The inlinee source lines, sorted by inlinee, then file, then line number.
30 /// searches over all inlinees. We prefer binary search over a hashmap as LLVM somtimes outputs30 /// This saves us from repeatedly doing linear searches over all inlinees.
31 /// multiple entries for a single inlinee ID, see `getInlineeSourceLines` for more info.31 /// We prefer binary search over a hashmap as LLVM somtimes outputs multiple entries
32 inlinee_source_lines: []InlineeSourceLine,32 /// for a single inlinee ID, see `getInlineeSourceLines` for more info.
33 inlinee_source_lines: []*align(1) const pdb.InlineeSourceLine,
3334
34 pub fn deinit(self: *Module, allocator: Allocator) void {35 pub fn deinit(self: *Module, allocator: Allocator) void {
35 allocator.free(self.module_name);36 allocator.free(self.module_name);
...@@ -669,44 +670,68 @@ pub fn getSymbolName(self: *Pdb, proc_sym: *align(1) const pdb.ProcSym) []const...@@ -669,44 +670,68 @@ pub fn getSymbolName(self: *Pdb, proc_sym: *align(1) const pdb.ProcSym) []const
669 return std.mem.sliceTo(@as([*:0]const u8, @ptrCast(&proc_sym.name[0])), 0);670 return std.mem.sliceTo(@as([*:0]const u8, @ptrCast(&proc_sym.name[0])), 0);
670}671}
671672
672pub const InlineeSourceLine = struct {673fn inlineeSourceLineLessThan(
673 signature: pdb.InlineeSourceLineSignature,674 _: void,
674 info: *align(1) const pdb.InlineeSourceLine,675 lhs: *align(1) const pdb.InlineeSourceLine,
676 rhs: *align(1) const pdb.InlineeSourceLine,
677) bool {
678 if (lhs.inlinee < rhs.inlinee) return true;
679 if (lhs.inlinee > rhs.inlinee) return false;
680 if (lhs.file_id < rhs.file_id) return true;
681 if (lhs.file_id > rhs.file_id) return false;
682 return lhs.source_line_num < rhs.source_line_num;
683}
675684
676 fn lessThan(_: void, lhs: InlineeSourceLine, rhs: InlineeSourceLine) bool {685fn compareInlineeSourceLineInlinee(
677 return lhs.info.inlinee < rhs.info.inlinee;686 inlinee: u32,
678 }687 inlinee_src_line: *align(1) const pdb.InlineeSourceLine,
688) std.math.Order {
689 return std.math.order(inlinee, inlinee_src_line.inlinee);
690}
679691
680 fn compare(inlinee: u32, self: InlineeSourceLine) std.math.Order {692pub const InlineeSourceLocationIterator = struct {
681 return std.math.order(inlinee, self.info.inlinee);693 /// The iterator assumes that all source lines in the slice are associated
694 /// with the same inlinee, and that it is sorted by file, then line number.
695 lines: []*align(1) const pdb.InlineeSourceLine,
696
697 pub const empty: InlineeSourceLocationIterator = .{ .lines = &.{} };
698
699 pub fn next(iter: *InlineeSourceLocationIterator) ?*align(1) const pdb.InlineeSourceLine {
700 if (iter.lines.len == 0) return null;
701 const line = iter.lines[0];
702 iter.lines = iter.lines[1..];
703 // Filter out duplicate entries
704 while (iter.lines.len != 0 and
705 iter.lines[0].file_id == line.file_id and
706 iter.lines[0].source_line_num == line.source_line_num)
707 {
708 iter.lines = iter.lines[1..];
709 }
710 return line;
682 }711 }
683};712};
684713
685/// Returns all `InlineeSourceLine`s for a given module with the given inlinee. Ideally there would714/// Returns all `pdb.InlineeSourceLine`s for a given module with the given inlinee. Ideally
686/// only be one entry per inlinee, but LLVM appears to assign all functions that share a name the715/// there would only be one entry per inlinee, but LLVM appears to assign all functions that share
687/// same inlinee ID. This appears to be a bug, so the best the caller can do right now is print all716/// a name the same inlinee ID. This is a bug: https://github.com/llvm/llvm-project/issues/191787
688/// the results.717/// The best the caller can do right now is print all the results.
689pub fn getInlineeSourceLines(718pub fn getInlineeSourceLines(self: *Pdb, mod: *Module, inlinee: u32) InlineeSourceLocationIterator {
690 self: *Pdb,
691 mod: *Module,
692 inlinee: u32,
693) []const InlineeSourceLine {
694 _ = self;719 _ = self;
695720
696 // Binary search to an arbitrary match, if there are other matches they will be adjacent721 // Binary search to an arbitrary match, if there are other matches they will be adjacent
697 const any = std.sort.binarySearch(722 const any = std.sort.binarySearch(
698 InlineeSourceLine,723 *align(1) const pdb.InlineeSourceLine,
699 mod.inlinee_source_lines,724 mod.inlinee_source_lines,
700 inlinee,725 inlinee,
701 InlineeSourceLine.compare,726 compareInlineeSourceLineInlinee,
702 ) orelse return &.{};727 ) orelse return .empty;
703728
704 // Linearly scan to the first match729 // Linearly scan to the first match
705 const begin = b: {730 const begin = b: {
706 var begin = any;731 var begin = any;
707 while (begin > 0) {732 while (begin > 0) {
708 const prev = begin - 1;733 const prev = begin - 1;
709 if (mod.inlinee_source_lines[prev].info.inlinee != inlinee) break;734 if (mod.inlinee_source_lines[prev].inlinee != inlinee) break;
710 begin = prev;735 begin = prev;
711 }736 }
712 break :b begin;737 break :b begin;
...@@ -716,13 +741,13 @@ pub fn getInlineeSourceLines(...@@ -716,13 +741,13 @@ pub fn getInlineeSourceLines(
716 const end = b: {741 const end = b: {
717 var end = any + 1;742 var end = any + 1;
718 while (end < mod.inlinee_source_lines.len and743 while (end < mod.inlinee_source_lines.len and
719 mod.inlinee_source_lines[end].info.inlinee == inlinee) : (end += 1)744 mod.inlinee_source_lines[end].inlinee == inlinee) : (end += 1)
720 {}745 {}
721 break :b end;746 break :b end;
722 };747 };
723748
724 // Return a slice of all the matches749 // Return an iterator over all matches (the iterator filters out duplicate entries)
725 return mod.inlinee_source_lines[begin..end];750 return .{ .lines = mod.inlinee_source_lines[begin..end] };
726}751}
727752
728pub fn getLineNumberInfo(self: *Pdb, gpa: Allocator, module: *Module, address: u64) !std.debug.SourceLocation {753pub fn getLineNumberInfo(self: *Pdb, gpa: Allocator, module: *Module, address: u64) !std.debug.SourceLocation {
...@@ -845,7 +870,7 @@ pub fn getModule(self: *Pdb, index: usize) !?*Module {...@@ -845,7 +870,7 @@ pub fn getModule(self: *Pdb, index: usize) !?*Module {
845 mod.subsect_info = try reader.readAlloc(gpa, mod.mod_info.c13_byte_size);870 mod.subsect_info = try reader.readAlloc(gpa, mod.mod_info.c13_byte_size);
846 errdefer gpa.free(mod.subsect_info);871 errdefer gpa.free(mod.subsect_info);
847 mod.inlinee_source_lines = b: {872 mod.inlinee_source_lines = b: {
848 var inlinee_source_lines: std.ArrayList(InlineeSourceLine) = .empty;873 var inlinee_source_lines: std.ArrayList(*align(1) const pdb.InlineeSourceLine) = .empty;
849 defer inlinee_source_lines.deinit(gpa);874 defer inlinee_source_lines.deinit(gpa);
850 var subsects: Io.Reader = .fixed(mod.subsect_info);875 var subsects: Io.Reader = .fixed(mod.subsect_info);
851 while (subsects.takeStructPointer(pdb.DebugSubsectionHeader) catch null) |subsect_hdr| {876 while (subsects.takeStructPointer(pdb.DebugSubsectionHeader) catch null) |subsect_hdr| {
...@@ -866,15 +891,17 @@ pub fn getModule(self: *Pdb, index: usize) !?*Module {...@@ -866,15 +891,17 @@ pub fn getModule(self: *Pdb, index: usize) !?*Module {
866 return error.InvalidDebugInfo;891 return error.InvalidDebugInfo;
867 }892 }
868893
869 try inlinee_source_lines.append(gpa, .{894 try inlinee_source_lines.append(gpa, info);
870 .signature = inlinee_source_line_signature,
871 .info = info,
872 });
873 }895 }
874 }896 }
875 }897 }
876898
877 std.mem.sortUnstable(InlineeSourceLine, inlinee_source_lines.items, {}, InlineeSourceLine.lessThan);899 std.mem.sortUnstable(
900 *align(1) const pdb.InlineeSourceLine,
901 inlinee_source_lines.items,
902 {},
903 inlineeSourceLineLessThan,
904 );
878 break :b try inlinee_source_lines.toOwnedSlice(gpa);905 break :b try inlinee_source_lines.toOwnedSlice(gpa);
879 };906 };
880 errdefer gpa.free(mod.inlinee_source_lines);907 errdefer gpa.free(mod.inlinee_source_lines);
lib/std/debug/SelfInfo/Windows.zig+3-5
...@@ -311,15 +311,13 @@ const Module = struct {...@@ -311,15 +311,13 @@ const Module = struct {
311311
312 // If our address points into this site, get the source location(s) it312 // If our address points into this site, get the source location(s) it
313 // points at313 // points at
314 for (pdb.getInlineeSourceLines(314 var line_iter = pdb.getInlineeSourceLines(module, inline_site.inlinee);
315 module,315 while (line_iter.next()) |inlinee_src_line| {
316 inline_site.inlinee,
317 )) |inlinee_src_line| {
318 const maybe_loc = pdb.getInlineSiteSourceLocation(316 const maybe_loc = pdb.getInlineSiteSourceLocation(
319 text_arena,317 text_arena,
320 module,318 module,
321 inline_site,319 inline_site,
322 inlinee_src_line.info,320 inlinee_src_line,
323 offset_in_func,321 offset_in_func,
324 ) catch continue;322 ) catch continue;
325 const loc = maybe_loc orelse continue;323 const loc = maybe_loc orelse continue;
test/error_traces.zig+17-14
...@@ -580,12 +580,15 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -580,12 +580,15 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
580580
581 cases.addCase(.{581 cases.addCase(.{
582 .name = "trace through inline call",582 .name = "trace through inline call",
583 // The main function has two inline calls to ensure
584 // that inlinees in PDBs are properly deduplicated.
583 .source =585 .source =
584 \\pub fn main() !void {586 \\pub fn main() !void {
585 \\ try foo();587 \\ try foo(false);
588 \\ try foo(true);
586 \\}589 \\}
587 \\inline fn foo() !void {590 \\inline fn foo(b: bool) !void {
588 \\ try bar();591 \\ if (b) try bar();
589 \\}592 \\}
590 \\fn bar() !void {593 \\fn bar() !void {
591 \\ return error.ThisIsSoSad;594 \\ return error.ThisIsSoSad;
...@@ -597,25 +600,25 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target....@@ -597,25 +600,25 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.
597 // so our expected result is slightly different for Windows than on other operating600 // so our expected result is slightly different for Windows than on other operating
598 // systems.601 // systems.
599 .windows =>602 .windows =>
600 \\source.zig:8:5: [address] in bar603 \\source.zig:9:5: [address] in bar
601 \\ return error.ThisIsSoSad;604 \\ return error.ThisIsSoSad;
602 \\ ^605 \\ ^
603 \\source.zig:5: [address] in foo606 \\source.zig:6: [address] in foo
604 \\ try bar();607 \\ if (b) try bar();
605 \\608 \\
606 \\source.zig:2:5: [address] in main609 \\source.zig:3:5: [address] in main
607 \\ try foo();610 \\ try foo(true);
608 \\ ^611 \\ ^
609 ,612 ,
610 else =>613 else =>
611 \\source.zig:8:5: [address] in bar614 \\source.zig:9:5: [address] in bar
612 \\ return error.ThisIsSoSad;615 \\ return error.ThisIsSoSad;
613 \\ ^616 \\ ^
614 \\source.zig:5:5: [address] in foo617 \\source.zig:6:12: [address] in foo
615 \\ try bar();618 \\ if (b) try bar();
616 \\ ^619 \\ ^
617 \\source.zig:2:5: [address] in main620 \\source.zig:3:5: [address] in main
618 \\ try foo();621 \\ try foo(true);
619 \\ ^622 \\ ^
620 ,623 ,
621 },624 },
test/stack_traces.zig+13-10
...@@ -226,12 +226,15 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target....@@ -226,12 +226,15 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target.
226226
227 cases.addCase(.{227 cases.addCase(.{
228 .name = "simple inline panic",228 .name = "simple inline panic",
229 // The main function has two inline calls to ensure
230 // that inlinees in PDBs are properly deduplicated.
229 .source =231 .source =
230 \\pub fn main() void {232 \\pub fn main() void {
231 \\ foo();233 \\ foo(false);
234 \\ foo(true);
232 \\}235 \\}
233 \\inline fn foo() void {236 \\inline fn foo(b: bool) void {
234 \\ @panic("oh no");237 \\ if (b) @panic("oh no");
235 \\}238 \\}
236 \\239 \\
237 ,240 ,
...@@ -242,11 +245,11 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target....@@ -242,11 +245,11 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target.
242 // so the first location has only a row.245 // so the first location has only a row.
243 .windows =>246 .windows =>
244 \\panic: oh no247 \\panic: oh no
245 \\source.zig:5: [address] in foo248 \\source.zig:6: [address] in foo
246 \\ @panic("oh no");249 \\ if (b) @panic("oh no");
247 \\250 \\
248 \\source.zig:2:8: [address] in main251 \\source.zig:3:8: [address] in main
249 \\ foo();252 \\ foo(true);
250 \\ ^253 \\ ^
251 \\254 \\
252 ,255 ,
...@@ -254,9 +257,9 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target....@@ -254,9 +257,9 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target.
254 // resolve the inline callers.257 // resolve the inline callers.
255 else =>258 else =>
256 \\panic: oh no259 \\panic: oh no
257 \\source.zig:5:5: [address] in foo260 \\source.zig:6:12: [address] in foo
258 \\ @panic("oh no");261 \\ if (b) @panic("oh no");
259 \\ ^262 \\ ^
260 ,263 ,
261 },264 },
262 .expect_strip = switch (os) {265 .expect_strip = switch (os) {