aboutsummaryrefslogtreecommitdiff
path: root/src/link/Wasm.zig
diff options
context:
space:
mode:
authorLuuk de Gram <luuk@degram.dev>2022-09-06 20:21:33 +0200
committerLuuk de Gram <luuk@degram.dev>2022-09-07 18:59:36 +0200
commita8d137d05ae36870d4e896cf5e37b591d9fa219c (patch)
tree720dde8e8f150bb6bb2e63e5506ca337e50eee3d /src/link/Wasm.zig
parent971327d6e0a2cdcfd1a7695d1dea86dbbebd730e (diff)
downloadzig-a8d137d05ae36870d4e896cf5e37b591d9fa219c.tar.gz
zig-a8d137d05ae36870d4e896cf5e37b591d9fa219c.zip
wasm-linker: support incremental debug info
Although the wasm-linker previously already supported debug information in incremental-mode, this was no longer working as-is with the addition of supporting object-file-parsed debug information. This commit implements the Zig-created debug information structure from scratch which is a lot more robust and also allows being linked with debug information from other object files.
Diffstat (limited to 'src/link/Wasm.zig')
-rw-r--r--src/link/Wasm.zig156
1 files changed, 112 insertions, 44 deletions
diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig
index 67f229ca84..0c5f0e810f 100644
--- a/src/link/Wasm.zig
+++ b/src/link/Wasm.zig
@@ -95,9 +95,10 @@ imports: std.AutoHashMapUnmanaged(SymbolLoc, types.Import) = .{},
segments: std.ArrayListUnmanaged(Segment) = .{},
/// Maps a data segment key (such as .rodata) to the index into `segments`.
data_segments: std.StringArrayHashMapUnmanaged(u32) = .{},
-/// A list of `types.Segment` which provide meta data
-/// about a data symbol such as its name
-segment_info: std.ArrayListUnmanaged(types.Segment) = .{},
+/// A table of `types.Segment` which provide meta data
+/// about a data symbol such as its name where the key is
+/// the segment index, which can be found from `data_segments`
+segment_info: std.AutoArrayHashMapUnmanaged(u32, types.Segment) = .{},
/// Deduplicated string table for strings used by symbols, imports and exports.
string_table: StringTable = .{},
/// Debug information for wasm
@@ -158,6 +159,19 @@ export_names: std.AutoHashMapUnmanaged(SymbolLoc, u32) = .{},
/// The actual table is populated during `flush`.
error_table_symbol: ?u32 = null,
+// Debug section atoms. These are only set when the current compilation
+// unit contains Zig code. The lifetime of these atoms are extended
+// until the end of the compiler's lifetime. Meaning they're not freed
+// during `flush()` in incremental-mode.
+debug_info_atom: ?*Atom = null,
+debug_line_atom: ?*Atom = null,
+debug_loc_atom: ?*Atom = null,
+debug_ranges_atom: ?*Atom = null,
+debug_abbrev_atom: ?*Atom = null,
+debug_str_atom: ?*Atom = null,
+debug_pubnames_atom: ?*Atom = null,
+debug_pubtypes_atom: ?*Atom = null,
+
pub const Segment = struct {
alignment: u32,
size: u32,
@@ -384,15 +398,16 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Wasm {
/// and symbols come from the object files instead.
pub fn initDebugSections(self: *Wasm) !void {
if (self.dwarf == null) return; // not compiling Zig code, so no need to pre-initialize debug sections
+ assert(self.debug_info_index == null);
// this will create an Atom and set the index for us.
- try self.createDebugSectionForIndex(&self.debug_info_index);
- try self.createDebugSectionForIndex(&self.debug_line_index);
- try self.createDebugSectionForIndex(&self.debug_loc_index);
- try self.createDebugSectionForIndex(&self.debug_abbrev_index);
- try self.createDebugSectionForIndex(&self.debug_ranges_index);
- try self.createDebugSectionForIndex(&self.debug_str_index);
- try self.createDebugSectionForIndex(&self.debug_pubnames_index);
- try self.createDebugSectionForIndex(&self.debug_pubtypes_index);
+ self.debug_info_atom = try self.createDebugSectionForIndex(&self.debug_info_index, ".debug_info");
+ self.debug_line_atom = try self.createDebugSectionForIndex(&self.debug_line_index, ".debug_line");
+ self.debug_loc_atom = try self.createDebugSectionForIndex(&self.debug_loc_index, ".debug_loc");
+ self.debug_abbrev_atom = try self.createDebugSectionForIndex(&self.debug_abbrev_index, ".debug_abbrev");
+ self.debug_ranges_atom = try self.createDebugSectionForIndex(&self.debug_ranges_index, ".debug_ranges");
+ self.debug_str_atom = try self.createDebugSectionForIndex(&self.debug_str_index, ".debug_str");
+ self.debug_pubnames_atom = try self.createDebugSectionForIndex(&self.debug_pubnames_index, ".debug_pubnames");
+ self.debug_pubtypes_atom = try self.createDebugSectionForIndex(&self.debug_pubtypes_index, ".debug_pubtypes");
}
fn parseInputFiles(self: *Wasm, files: []const []const u8) !void {
@@ -676,7 +691,7 @@ pub fn deinit(self: *Wasm) void {
for (self.func_types.items) |*func_type| {
func_type.deinit(gpa);
}
- for (self.segment_info.items) |segment_info| {
+ for (self.segment_info.values()) |segment_info| {
gpa.free(segment_info.name);
}
for (self.objects.items) |*object| {
@@ -1364,16 +1379,7 @@ fn parseAtom(self: *Wasm, atom: *Atom, kind: Kind) !void {
const index = gop.value_ptr.*;
self.segments.items[index].size += atom.size;
- // segment indexes can be off by 1 due to also containing a segment
- // for the code section, so we must check if the existing segment
- // is larger than that of the code section, and substract the index by 1 in such case.
- var info_add = if (self.code_section_index) |idx| blk: {
- if (idx < index) break :blk @as(u32, 1);
- break :blk 0;
- } else @as(u32, 0);
- if (self.debug_info_index != null) info_add += 1;
- if (self.debug_line_index != null) info_add += 1;
- symbol.index = index - info_add;
+ symbol.index = @intCast(u32, self.segment_info.getIndex(index).?);
// segment info already exists, so free its memory
self.base.allocator.free(segment_name);
break :result index;
@@ -1386,8 +1392,8 @@ fn parseAtom(self: *Wasm, atom: *Atom, kind: Kind) !void {
});
gop.value_ptr.* = index;
- const info_index = @intCast(u32, self.segment_info.items.len);
- try self.segment_info.append(self.base.allocator, segment_info);
+ const info_index = @intCast(u32, self.segment_info.count());
+ try self.segment_info.put(self.base.allocator, index, segment_info);
symbol.index = info_index;
break :result index;
}
@@ -1397,18 +1403,54 @@ fn parseAtom(self: *Wasm, atom: *Atom, kind: Kind) !void {
const segment: *Segment = &self.segments.items[final_index];
segment.alignment = std.math.max(segment.alignment, atom.alignment);
- if (self.atoms.getPtr(final_index)) |last| {
+ try self.appendAtomAtIndex(final_index, atom);
+}
+
+/// From a given index, append the given `Atom` at the back of the linked list.
+/// Simply inserts it into the map of atoms when it doesn't exist yet.
+pub fn appendAtomAtIndex(self: *Wasm, index: u32, atom: *Atom) !void {
+ if (self.atoms.getPtr(index)) |last| {
last.*.next = atom;
atom.prev = last.*;
last.* = atom;
} else {
- try self.atoms.putNoClobber(self.base.allocator, final_index, atom);
+ try self.atoms.putNoClobber(self.base.allocator, index, atom);
}
}
+/// Allocates debug atoms into their respective debug sections
+/// to merge them with maybe-existing debug atoms from object files.
+fn allocateDebugAtoms(self: *Wasm) !void {
+ if (self.dwarf == null) return;
+
+ const allocAtom = struct {
+ fn f(bin: *Wasm, maybe_index: *?u32, atom: *Atom) !void {
+ const index = maybe_index.* orelse idx: {
+ const index = @intCast(u32, bin.segments.items.len);
+ try bin.appendDummySegment();
+ maybe_index.* = index;
+ break :idx index;
+ };
+ atom.size = @intCast(u32, atom.code.items.len);
+ bin.symbols.items[atom.sym_index].index = index;
+ try bin.appendAtomAtIndex(index, atom);
+ }
+ }.f;
+
+ try allocAtom(self, &self.debug_info_index, self.debug_info_atom.?);
+ try allocAtom(self, &self.debug_line_index, self.debug_line_atom.?);
+ try allocAtom(self, &self.debug_loc_index, self.debug_loc_atom.?);
+ try allocAtom(self, &self.debug_str_index, self.debug_str_atom.?);
+ try allocAtom(self, &self.debug_ranges_index, self.debug_ranges_atom.?);
+ try allocAtom(self, &self.debug_abbrev_index, self.debug_abbrev_atom.?);
+ try allocAtom(self, &self.debug_pubnames_index, self.debug_pubnames_atom.?);
+ try allocAtom(self, &self.debug_pubtypes_index, self.debug_pubtypes_atom.?);
+}
+
fn allocateAtoms(self: *Wasm) !void {
// first sort the data segments
try sortDataSegments(self);
+ try allocateDebugAtoms(self);
var it = self.atoms.iterator();
while (it.next()) |entry| {
@@ -1426,7 +1468,7 @@ fn allocateAtoms(self: *Wasm) !void {
atom.size,
});
offset += atom.size;
- self.symbol_atom.putAssumeCapacity(atom.symbolLoc(), atom); // Update atom pointers
+ try self.symbol_atom.put(self.base.allocator, atom.symbolLoc(), atom); // Update atom pointers
atom = atom.next orelse break;
}
segment.size = std.mem.alignForwardGeneric(u32, offset, segment.alignment);
@@ -1989,20 +2031,35 @@ fn populateErrorNameTable(self: *Wasm) !void {
/// From a given index variable, creates a new debug section.
/// This initializes the index, appends a new segment,
/// and finally, creates a managed `Atom`.
-pub fn createDebugSectionForIndex(self: *Wasm, index: *?u32) !void {
+pub fn createDebugSectionForIndex(self: *Wasm, index: *?u32, name: []const u8) !*Atom {
const new_index = @intCast(u32, self.segments.items.len);
index.* = new_index;
try self.appendDummySegment();
+ // _ = index;
+
+ const sym_index = self.symbols_free_list.popOrNull() orelse idx: {
+ const tmp_index = @intCast(u32, self.symbols.items.len);
+ _ = try self.symbols.addOne(self.base.allocator);
+ break :idx tmp_index;
+ };
+ self.symbols.items[sym_index] = .{
+ .tag = .section,
+ .name = try self.string_table.put(self.base.allocator, name),
+ .index = 0,
+ .flags = @enumToInt(Symbol.Flag.WASM_SYM_BINDING_LOCAL),
+ };
const atom = try self.base.allocator.create(Atom);
atom.* = Atom.empty;
atom.alignment = 1; // debug sections are always 1-byte-aligned
+ atom.sym_index = sym_index;
try self.managed_atoms.append(self.base.allocator, atom);
- try self.atoms.put(self.base.allocator, new_index, atom);
+ try self.symbol_atom.put(self.base.allocator, atom.symbolLoc(), atom);
+ return atom;
}
fn resetState(self: *Wasm) void {
- for (self.segment_info.items) |*segment_info| {
+ for (self.segment_info.values()) |segment_info| {
self.base.allocator.free(segment_info.name);
}
if (self.base.options.module) |mod| {
@@ -2029,6 +2086,12 @@ fn resetState(self: *Wasm) void {
self.code_section_index = null;
self.debug_info_index = null;
self.debug_line_index = null;
+ self.debug_loc_index = null;
+ self.debug_str_index = null;
+ self.debug_ranges_index = null;
+ self.debug_abbrev_index = null;
+ self.debug_pubnames_index = null;
+ self.debug_pubtypes_index = null;
}
pub fn flush(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !void {
@@ -2508,26 +2571,31 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
var debug_bytes = std.ArrayList(u8).init(self.base.allocator);
defer debug_bytes.deinit();
- const debug_sections = .{
- .{ ".debug_info", self.debug_info_index },
- .{ ".debug_pubtypes", self.debug_pubtypes_index },
- .{ ".debug_abbrev", self.debug_abbrev_index },
- .{ ".debug_line", self.debug_line_index },
- .{ ".debug_str", self.debug_str_index },
- .{ ".debug_pubnames", self.debug_pubnames_index },
- .{ ".debug_loc", self.debug_loc_index },
- .{ ".debug_ranges", self.debug_ranges_index },
+ const DebugSection = struct {
+ name: []const u8,
+ index: ?u32,
+ };
+
+ const debug_sections: []const DebugSection = &.{
+ .{ .name = ".debug_info", .index = self.debug_info_index },
+ .{ .name = ".debug_pubtypes", .index = self.debug_pubtypes_index },
+ .{ .name = ".debug_abbrev", .index = self.debug_abbrev_index },
+ .{ .name = ".debug_line", .index = self.debug_line_index },
+ .{ .name = ".debug_str", .index = self.debug_str_index },
+ .{ .name = ".debug_pubnames", .index = self.debug_pubnames_index },
+ .{ .name = ".debug_loc", .index = self.debug_loc_index },
+ .{ .name = ".debug_ranges", .index = self.debug_ranges_index },
};
- inline for (debug_sections) |item| {
- if (item[1]) |index| {
+ for (debug_sections) |item| {
+ if (item.index) |index| {
var atom = self.atoms.get(index).?.getFirst();
while (true) {
atom.resolveRelocs(self);
try debug_bytes.appendSlice(atom.code.items);
atom = atom.next orelse break;
}
- try emitDebugSection(file, debug_bytes.items, item[0]);
+ try emitDebugSection(file, debug_bytes.items, item.name);
debug_bytes.clearRetainingCapacity();
}
}
@@ -3242,8 +3310,8 @@ fn emitSegmentInfo(self: *Wasm, file: fs.File, arena: Allocator) !void {
var payload = std.ArrayList(u8).init(arena);
const writer = payload.writer();
try leb.writeULEB128(file.writer(), @enumToInt(types.SubsectionType.WASM_SEGMENT_INFO));
- try leb.writeULEB128(writer, @intCast(u32, self.segment_info.items.len));
- for (self.segment_info.items) |segment_info| {
+ try leb.writeULEB128(writer, @intCast(u32, self.segment_info.count()));
+ for (self.segment_info.values()) |segment_info| {
log.debug("Emit segment: {s} align({d}) flags({b})", .{
segment_info.name,
@ctz(segment_info.alignment),