authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-02 23:31:28-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-07 00:48:32-07:00
log1792258dc813cde7083fd7860442e6ec92afd4ba
treee338b76af71a6124bc67a6877659d1634d2ff5e1
parent66954e833051872308641b3a1af12aa865d5d59a

std.debug.Dwarf: precompute .debug_line table

yields a 60x speedup for resolveSourceLocations in debug builds

3 files changed, 150 insertions(+), 166 deletions(-)

lib/std/debug.zig+1-1
......@@ -762,7 +762,7 @@ pub fn writeCurrentStackTrace(
762762 // an overflow. We do not need to signal `StackIterator` as it will correctly detect this
763763 // condition on the subsequent iteration and return `null` thus terminating the loop.
764764 // same behaviour for x86-windows-msvc
765 const address = if (return_address == 0) return_address else return_address - 1;
765 const address = return_address -| 1;
766766 try printSourceAtAddress(debug_info, out_stream, address, tty_config);
767767 } else printLastUnwindError(&it, debug_info, out_stream, tty_config);
768768}
lib/std/debug/Dwarf.zig+146-164
......@@ -138,6 +138,29 @@ pub const CompileUnit = struct {
138138 rnglists_base: usize,
139139 loclists_base: usize,
140140 frame_base: ?*const FormValue,
141
142 src_loc_cache: ?SrcLocCache,
143
144 pub const SrcLocCache = struct {
145 line_table: LineTable,
146 directories: []const FileEntry,
147 files: []FileEntry,
148 version: u16,
149
150 pub const LineTable = std.AutoArrayHashMapUnmanaged(u64, LineEntry);
151
152 pub const LineEntry = struct {
153 line: u32,
154 column: u32,
155 file: u32,
156 };
157
158 pub fn findSource(slc: *const SrcLocCache, address: u64) !LineEntry {
159 const index = std.sort.upperBound(u64, address, slc.line_table.keys(), {}, std.sort.asc(u64));
160 if (index == 0) return missing();
161 return slc.line_table.values()[index - 1];
162 }
163 };
141164};
142165
143166pub const FormValue = union(enum) {
......@@ -760,6 +783,11 @@ pub fn deinit(di: *Dwarf, gpa: Allocator) void {
760783 }
761784 di.abbrev_table_list.deinit(gpa);
762785 for (di.compile_unit_list.items) |*cu| {
786 if (cu.src_loc_cache) |*slc| {
787 slc.line_table.deinit(gpa);
788 gpa.free(slc.directories);
789 gpa.free(slc.files);
790 }
763791 cu.die.deinit(gpa);
764792 }
765793 di.compile_unit_list.deinit(gpa);
......@@ -846,6 +874,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) ScanError!void {
846874 .rnglists_base = 0,
847875 .loclists_base = 0,
848876 .frame_base = null,
877 .src_loc_cache = null,
849878 };
850879
851880 while (true) {
......@@ -1032,6 +1061,7 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) ScanError!void {
10321061 .rnglists_base = if (compile_unit_die.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0,
10331062 .loclists_base = if (compile_unit_die.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0,
10341063 .frame_base = compile_unit_die.getAttr(AT.frame_base),
1064 .src_loc_cache = null,
10351065 };
10361066
10371067 compile_unit.pc_range = x: {
......@@ -1242,7 +1272,7 @@ const DebugRangeIterator = struct {
12421272};
12431273
12441274/// TODO: change this to binary searching the sorted compile unit list
1245pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*const CompileUnit {
1275pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*CompileUnit {
12461276 for (di.compile_unit_list.items) |*compile_unit| {
12471277 if (compile_unit.pc_range) |range| {
12481278 if (target_address >= range.start and target_address < range.end) return compile_unit;
......@@ -1352,34 +1382,36 @@ fn parseDie(
13521382 };
13531383}
13541384
1355pub fn getLineNumberInfo(
1356 di: *Dwarf,
1357 allocator: Allocator,
1358 compile_unit: CompileUnit,
1359 target_address: u64,
1360) !std.debug.SourceLocation {
1361 const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.section(.debug_line_str), compile_unit);
1385fn runLineNumberProgram(d: *Dwarf, gpa: Allocator, compile_unit: *CompileUnit) !CompileUnit.SrcLocCache {
1386 const compile_unit_cwd = try compile_unit.die.getAttrString(d, AT.comp_dir, d.section(.debug_line_str), compile_unit.*);
13621387 const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list);
13631388
1364 var fbr: FixedBufferReader = .{ .buf = di.section(.debug_line).?, .endian = di.endian };
1389 var fbr: FixedBufferReader = .{
1390 .buf = d.section(.debug_line).?,
1391 .endian = d.endian,
1392 };
13651393 try fbr.seekTo(line_info_offset);
13661394
13671395 const unit_header = try readUnitHeader(&fbr, null);
13681396 if (unit_header.unit_length == 0) return missing();
1397
13691398 const next_offset = unit_header.header_length + unit_header.unit_length;
13701399
13711400 const version = try fbr.readInt(u16);
13721401 if (version < 2) return bad();
13731402
1374 var addr_size: u8 = switch (unit_header.format) {
1375 .@"32" => 4,
1376 .@"64" => 8,
1403 const addr_size: u8, const seg_size: u8 = if (version >= 5) .{
1404 try fbr.readByte(),
1405 try fbr.readByte(),
1406 } else .{
1407 switch (unit_header.format) {
1408 .@"32" => 4,
1409 .@"64" => 8,
1410 },
1411 0,
13771412 };
1378 var seg_size: u8 = 0;
1379 if (version >= 5) {
1380 addr_size = try fbr.readByte();
1381 seg_size = try fbr.readByte();
1382 }
1413 _ = addr_size;
1414 _ = seg_size;
13831415
13841416 const prologue_length = try fbr.readAddress(unit_header.format);
13851417 const prog_start_offset = fbr.pos + prologue_length;
......@@ -1388,8 +1420,8 @@ pub fn getLineNumberInfo(
13881420 if (minimum_instruction_length == 0) return bad();
13891421
13901422 if (version >= 4) {
1391 // maximum_operations_per_instruction
1392 _ = try fbr.readByte();
1423 const maximum_operations_per_instruction = try fbr.readByte();
1424 _ = maximum_operations_per_instruction;
13931425 }
13941426
13951427 const default_is_stmt = (try fbr.readByte()) != 0;
......@@ -1402,18 +1434,18 @@ pub fn getLineNumberInfo(
14021434
14031435 const standard_opcode_lengths = try fbr.readBytes(opcode_base - 1);
14041436
1405 var include_directories = std.ArrayList(FileEntry).init(allocator);
1406 defer include_directories.deinit();
1407 var file_entries = std.ArrayList(FileEntry).init(allocator);
1408 defer file_entries.deinit();
1437 var directories: std.ArrayListUnmanaged(FileEntry) = .{};
1438 defer directories.deinit(gpa);
1439 var file_entries: std.ArrayListUnmanaged(FileEntry) = .{};
1440 defer file_entries.deinit(gpa);
14091441
14101442 if (version < 5) {
1411 try include_directories.append(.{ .path = compile_unit_cwd });
1443 try directories.append(gpa, .{ .path = compile_unit_cwd });
14121444
14131445 while (true) {
14141446 const dir = try fbr.readBytesTo(0);
14151447 if (dir.len == 0) break;
1416 try include_directories.append(.{ .path = dir });
1448 try directories.append(gpa, .{ .path = dir });
14171449 }
14181450
14191451 while (true) {
......@@ -1422,7 +1454,7 @@ pub fn getLineNumberInfo(
14221454 const dir_index = try fbr.readUleb128(u32);
14231455 const mtime = try fbr.readUleb128(u64);
14241456 const size = try fbr.readUleb128(u64);
1425 try file_entries.append(.{
1457 try file_entries.append(gpa, .{
14261458 .path = file_name,
14271459 .dir_index = dir_index,
14281460 .mtime = mtime,
......@@ -1446,52 +1478,10 @@ pub fn getLineNumberInfo(
14461478 }
14471479
14481480 const directories_count = try fbr.readUleb128(usize);
1449 try include_directories.ensureUnusedCapacity(directories_count);
1450 {
1451 var i: usize = 0;
1452 while (i < directories_count) : (i += 1) {
1453 var e: FileEntry = .{ .path = &.{} };
1454 for (dir_ent_fmt_buf[0..directory_entry_format_count]) |ent_fmt| {
1455 const form_value = try parseFormValue(
1456 &fbr,
1457 ent_fmt.form_code,
1458 unit_header.format,
1459 null,
1460 );
1461 switch (ent_fmt.content_type_code) {
1462 DW.LNCT.path => e.path = try form_value.getString(di.*),
1463 DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32),
1464 DW.LNCT.timestamp => e.mtime = try form_value.getUInt(u64),
1465 DW.LNCT.size => e.size = try form_value.getUInt(u64),
1466 DW.LNCT.MD5 => e.md5 = switch (form_value) {
1467 .data16 => |data16| data16.*,
1468 else => return bad(),
1469 },
1470 else => continue,
1471 }
1472 }
1473 include_directories.appendAssumeCapacity(e);
1474 }
1475 }
1476 }
14771481
1478 var file_ent_fmt_buf: [10]FileEntFmt = undefined;
1479 const file_name_entry_format_count = try fbr.readByte();
1480 if (file_name_entry_format_count > file_ent_fmt_buf.len) return bad();
1481 for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| {
1482 ent_fmt.* = .{
1483 .content_type_code = try fbr.readUleb128(u8),
1484 .form_code = try fbr.readUleb128(u16),
1485 };
1486 }
1487
1488 const file_names_count = try fbr.readUleb128(usize);
1489 try file_entries.ensureUnusedCapacity(file_names_count);
1490 {
1491 var i: usize = 0;
1492 while (i < file_names_count) : (i += 1) {
1493 var e: FileEntry = .{ .path = &.{} };
1494 for (file_ent_fmt_buf[0..file_name_entry_format_count]) |ent_fmt| {
1482 for (try directories.addManyAsSlice(gpa, directories_count)) |*e| {
1483 e.* = .{ .path = &.{} };
1484 for (dir_ent_fmt_buf[0..directory_entry_format_count]) |ent_fmt| {
14951485 const form_value = try parseFormValue(
14961486 &fbr,
14971487 ent_fmt.form_code,
......@@ -1499,7 +1489,7 @@ pub fn getLineNumberInfo(
14991489 null,
15001490 );
15011491 switch (ent_fmt.content_type_code) {
1502 DW.LNCT.path => e.path = try form_value.getString(di.*),
1492 DW.LNCT.path => e.path = try form_value.getString(d.*),
15031493 DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32),
15041494 DW.LNCT.timestamp => e.mtime = try form_value.getUInt(u64),
15051495 DW.LNCT.size => e.size = try form_value.getUInt(u64),
......@@ -1510,17 +1500,49 @@ pub fn getLineNumberInfo(
15101500 else => continue,
15111501 }
15121502 }
1513 file_entries.appendAssumeCapacity(e);
1503 }
1504 }
1505
1506 var file_ent_fmt_buf: [10]FileEntFmt = undefined;
1507 const file_name_entry_format_count = try fbr.readByte();
1508 if (file_name_entry_format_count > file_ent_fmt_buf.len) return bad();
1509 for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| {
1510 ent_fmt.* = .{
1511 .content_type_code = try fbr.readUleb128(u8),
1512 .form_code = try fbr.readUleb128(u16),
1513 };
1514 }
1515
1516 const file_names_count = try fbr.readUleb128(usize);
1517 try file_entries.ensureUnusedCapacity(gpa, file_names_count);
1518
1519 for (try file_entries.addManyAsSlice(gpa, file_names_count)) |*e| {
1520 e.* = .{ .path = &.{} };
1521 for (file_ent_fmt_buf[0..file_name_entry_format_count]) |ent_fmt| {
1522 const form_value = try parseFormValue(
1523 &fbr,
1524 ent_fmt.form_code,
1525 unit_header.format,
1526 null,
1527 );
1528 switch (ent_fmt.content_type_code) {
1529 DW.LNCT.path => e.path = try form_value.getString(d.*),
1530 DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32),
1531 DW.LNCT.timestamp => e.mtime = try form_value.getUInt(u64),
1532 DW.LNCT.size => e.size = try form_value.getUInt(u64),
1533 DW.LNCT.MD5 => e.md5 = switch (form_value) {
1534 .data16 => |data16| data16.*,
1535 else => return bad(),
1536 },
1537 else => continue,
1538 }
15141539 }
15151540 }
15161541 }
15171542
1518 var prog = LineNumberProgram.init(
1519 default_is_stmt,
1520 include_directories.items,
1521 target_address,
1522 version,
1523 );
1543 var prog = LineNumberProgram.init(default_is_stmt, version);
1544 var line_table: CompileUnit.SrcLocCache.LineTable = .{};
1545 errdefer line_table.deinit(gpa);
15241546
15251547 try fbr.seekTo(prog_start_offset);
15261548
......@@ -1536,7 +1558,7 @@ pub fn getLineNumberInfo(
15361558 switch (sub_op) {
15371559 DW.LNE.end_sequence => {
15381560 prog.end_sequence = true;
1539 if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info;
1561 try prog.addRow(gpa, &line_table);
15401562 prog.reset();
15411563 },
15421564 DW.LNE.set_address => {
......@@ -1548,7 +1570,7 @@ pub fn getLineNumberInfo(
15481570 const dir_index = try fbr.readUleb128(u32);
15491571 const mtime = try fbr.readUleb128(u64);
15501572 const size = try fbr.readUleb128(u64);
1551 try file_entries.append(.{
1573 try file_entries.append(gpa, .{
15521574 .path = path,
15531575 .dir_index = dir_index,
15541576 .mtime = mtime,
......@@ -1564,12 +1586,12 @@ pub fn getLineNumberInfo(
15641586 const inc_line = @as(i32, line_base) + @as(i32, adjusted_opcode % line_range);
15651587 prog.line += inc_line;
15661588 prog.address += inc_addr;
1567 if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info;
1589 try prog.addRow(gpa, &line_table);
15681590 prog.basic_block = false;
15691591 } else {
15701592 switch (opcode) {
15711593 DW.LNS.copy => {
1572 if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info;
1594 try prog.addRow(gpa, &line_table);
15731595 prog.basic_block = false;
15741596 },
15751597 DW.LNS.advance_pc => {
......@@ -1611,7 +1633,35 @@ pub fn getLineNumberInfo(
16111633 }
16121634 }
16131635
1614 return missing();
1636 return .{
1637 .line_table = line_table,
1638 .directories = try directories.toOwnedSlice(gpa),
1639 .files = try file_entries.toOwnedSlice(gpa),
1640 .version = version,
1641 };
1642}
1643
1644pub fn getLineNumberInfo(
1645 d: *Dwarf,
1646 gpa: Allocator,
1647 compile_unit: *CompileUnit,
1648 target_address: u64,
1649) !std.debug.SourceLocation {
1650 if (compile_unit.src_loc_cache == null)
1651 compile_unit.src_loc_cache = try runLineNumberProgram(d, gpa, compile_unit);
1652 const slc = &compile_unit.src_loc_cache.?;
1653 const entry = try slc.findSource(target_address);
1654 const file_index = entry.file - @intFromBool(slc.version < 5);
1655 if (file_index >= slc.files.len) return bad();
1656 const file_entry = &slc.files[file_index];
1657 if (file_entry.dir_index >= slc.directories.len) return bad();
1658 const dir_name = slc.directories[file_entry.dir_index].path;
1659 const file_name = try std.fs.path.join(gpa, &.{ dir_name, file_entry.path });
1660 return .{
1661 .line = entry.line,
1662 .column = entry.column,
1663 .file_name = file_name,
1664 };
16151665}
16161666
16171667fn getString(di: Dwarf, offset: u64) ![:0]const u8 {
......@@ -1826,17 +1876,6 @@ const LineNumberProgram = struct {
18261876 end_sequence: bool,
18271877
18281878 default_is_stmt: bool,
1829 target_address: u64,
1830 include_dirs: []const FileEntry,
1831
1832 prev_valid: bool,
1833 prev_address: u64,
1834 prev_file: usize,
1835 prev_line: i64,
1836 prev_column: u64,
1837 prev_is_stmt: bool,
1838 prev_basic_block: bool,
1839 prev_end_sequence: bool,
18401879
18411880 // Reset the state machine following the DWARF specification
18421881 pub fn reset(self: *LineNumberProgram) void {
......@@ -1847,24 +1886,10 @@ const LineNumberProgram = struct {
18471886 self.is_stmt = self.default_is_stmt;
18481887 self.basic_block = false;
18491888 self.end_sequence = false;
1850 // Invalidate all the remaining fields
1851 self.prev_valid = false;
1852 self.prev_address = 0;
1853 self.prev_file = undefined;
1854 self.prev_line = undefined;
1855 self.prev_column = undefined;
1856 self.prev_is_stmt = undefined;
1857 self.prev_basic_block = undefined;
1858 self.prev_end_sequence = undefined;
18591889 }
18601890
1861 pub fn init(
1862 is_stmt: bool,
1863 include_dirs: []const FileEntry,
1864 target_address: u64,
1865 version: u16,
1866 ) LineNumberProgram {
1867 return LineNumberProgram{
1891 pub fn init(is_stmt: bool, version: u16) LineNumberProgram {
1892 return .{
18681893 .address = 0,
18691894 .file = 1,
18701895 .line = 1,
......@@ -1873,60 +1898,17 @@ const LineNumberProgram = struct {
18731898 .is_stmt = is_stmt,
18741899 .basic_block = false,
18751900 .end_sequence = false,
1876 .include_dirs = include_dirs,
18771901 .default_is_stmt = is_stmt,
1878 .target_address = target_address,
1879 .prev_valid = false,
1880 .prev_address = 0,
1881 .prev_file = undefined,
1882 .prev_line = undefined,
1883 .prev_column = undefined,
1884 .prev_is_stmt = undefined,
1885 .prev_basic_block = undefined,
1886 .prev_end_sequence = undefined,
18871902 };
18881903 }
18891904
1890 pub fn checkLineMatch(
1891 self: *LineNumberProgram,
1892 allocator: Allocator,
1893 file_entries: []const FileEntry,
1894 ) !?std.debug.SourceLocation {
1895 if (self.prev_valid and
1896 self.target_address >= self.prev_address and
1897 self.target_address < self.address)
1898 {
1899 const file_index = if (self.version >= 5) self.prev_file else i: {
1900 if (self.prev_file == 0) return missing();
1901 break :i self.prev_file - 1;
1902 };
1903
1904 if (file_index >= file_entries.len) return bad();
1905 const file_entry = &file_entries[file_index];
1906
1907 if (file_entry.dir_index >= self.include_dirs.len) return bad();
1908 const dir_name = self.include_dirs[file_entry.dir_index].path;
1909
1910 const file_name = try std.fs.path.join(allocator, &[_][]const u8{
1911 dir_name, file_entry.path,
1912 });
1913
1914 return std.debug.SourceLocation{
1915 .line = if (self.prev_line >= 0) @as(u64, @intCast(self.prev_line)) else 0,
1916 .column = self.prev_column,
1917 .file_name = file_name,
1918 };
1919 }
1920
1921 self.prev_valid = true;
1922 self.prev_address = self.address;
1923 self.prev_file = self.file;
1924 self.prev_line = self.line;
1925 self.prev_column = self.column;
1926 self.prev_is_stmt = self.is_stmt;
1927 self.prev_basic_block = self.basic_block;
1928 self.prev_end_sequence = self.end_sequence;
1929 return null;
1905 pub fn addRow(prog: *LineNumberProgram, gpa: Allocator, table: *CompileUnit.SrcLocCache.LineTable) !void {
1906 if (prog.line == 0) return; // garbage data
1907 try table.put(gpa, prog.address, .{
1908 .line = cast(u32, prog.line) orelse maxInt(u32),
1909 .column = cast(u32, prog.column) orelse maxInt(u32),
1910 .file = cast(u32, prog.file) orelse return bad(),
1911 });
19301912 }
19311913};
19321914
......@@ -2381,7 +2363,7 @@ pub fn resolveSourceLocations(
23812363 defer prog_node.end();
23822364
23832365 var cu_i: usize = 0;
2384 var cu: *const CompileUnit = &d.compile_unit_list.items[0];
2366 var cu: *CompileUnit = &d.compile_unit_list.items[0];
23852367 var range = cu.pc_range.?;
23862368 next_pc: for (sorted_pc_addrs, output) |pc, *out| {
23872369 defer prog_node.completeOne();
......@@ -2403,7 +2385,7 @@ pub fn resolveSourceLocations(
24032385 }
24042386 // TODO: instead of calling this function, break the function up into one that parses the
24052387 // information once and prepares a context that can be reused for the entire batch.
2406 if (getLineNumberInfo(d, gpa, cu.*, pc)) |src_loc| {
2388 if (getLineNumberInfo(d, gpa, cu, pc)) |src_loc| {
24072389 out.* = src_loc;
24082390 } else |err| switch (err) {
24092391 error.MissingDebugInfo, error.InvalidDebugInfo => out.* = std.debug.SourceLocation.invalid,
......@@ -2419,7 +2401,7 @@ fn getSymbol(di: *Dwarf, allocator: Allocator, address: u64) !std.debug.Symbol {
24192401 .compile_unit_name = compile_unit.die.getAttrString(di, std.dwarf.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) {
24202402 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
24212403 },
2422 .source_location = di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) {
2404 .source_location = di.getLineNumberInfo(allocator, compile_unit, address) catch |err| switch (err) {
24232405 error.MissingDebugInfo, error.InvalidDebugInfo => null,
24242406 else => return err,
24252407 },
lib/std/debug/FixedBufferReader.zig+3-1
......@@ -1,4 +1,6 @@
1const std = @import("std.zig");
1//! Optimized for performance in debug builds.
2
3const std = @import("../std.zig");
24const MemoryAccessor = std.debug.MemoryAccessor;
35
46const FixedBufferReader = @This();