diff options
| author | Luuk de Gram <luuk@degram.dev> | 2022-09-08 16:57:23 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-09-08 16:57:23 +0200 |
| commit | ab4b26d8a6a36ccf75af8e25f0a1f7b88063b76f (patch) | |
| tree | 1a2922f3412bab348087e53c6962c8d7baa33b14 /src | |
| parent | 6a62a15ecde10300f6281e2d49fed34031d5f68a (diff) | |
| parent | a8d137d05ae36870d4e896cf5e37b591d9fa219c (diff) | |
| download | zig-ab4b26d8a6a36ccf75af8e25f0a1f7b88063b76f.tar.gz zig-ab4b26d8a6a36ccf75af8e25f0a1f7b88063b76f.zip | |
Merge pull request #12770 from Luukdegram/wasm-dwarf
wasm-linker: implement linking debug-information
Diffstat (limited to 'src')
| -rw-r--r-- | src/arch/wasm/CodeGen.zig | 4 | ||||
| -rw-r--r-- | src/link/Dwarf.zig | 73 | ||||
| -rw-r--r-- | src/link/Wasm.zig | 399 | ||||
| -rw-r--r-- | src/link/Wasm/Atom.zig | 42 | ||||
| -rw-r--r-- | src/link/Wasm/Object.zig | 109 |
5 files changed, 430 insertions, 197 deletions
diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index b9637bf8e3..a72ae6a423 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -666,6 +666,10 @@ pub fn deinit(self: *Self) void { self.locals.deinit(self.gpa); self.mir_instructions.deinit(self.gpa); self.mir_extra.deinit(self.gpa); + self.free_locals_i32.deinit(self.gpa); + self.free_locals_i64.deinit(self.gpa); + self.free_locals_f32.deinit(self.gpa); + self.free_locals_f64.deinit(self.gpa); self.* = undefined; } diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index 3ae151491f..474c822ae6 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -861,7 +861,8 @@ pub fn commitDeclState( }, .wasm => { const wasm_file = file.cast(File.Wasm).?; - writeDbgLineNopsBuffered(wasm_file.debug_line.items, src_fn.off, 0, &.{}, src_fn.len); + const debug_line = wasm_file.debug_line_atom.?.code; + writeDbgLineNopsBuffered(debug_line.items, src_fn.off, 0, &.{}, src_fn.len); }, else => unreachable, } @@ -972,23 +973,21 @@ pub fn commitDeclState( }, .wasm => { const wasm_file = file.cast(File.Wasm).?; - const segment_index = try wasm_file.getDebugLineIndex(); - const segment = &wasm_file.segments.items[segment_index]; - const debug_line = &wasm_file.debug_line; - if (needed_size != segment.size) { + const atom = wasm_file.debug_line_atom.?; + const debug_line = &atom.code; + const segment_size = debug_line.items.len; + if (needed_size != segment_size) { log.debug(" needed size does not equal allocated size: {d}", .{needed_size}); - if (needed_size > segment.size) { - log.debug(" allocating {d} bytes for 'debug line' information", .{needed_size - segment.size}); + if (needed_size > segment_size) { + log.debug(" allocating {d} bytes for 'debug line' information", .{needed_size - segment_size}); try debug_line.resize(self.allocator, needed_size); - mem.set(u8, debug_line.items[segment.size..], 0); + mem.set(u8, debug_line.items[segment_size..], 0); } - segment.size = needed_size; debug_line.items.len = needed_size; } - const offset = segment.offset + src_fn.off; writeDbgLineNopsBuffered( debug_line.items, - offset, + src_fn.off, prev_padding_size, dbg_line_buffer.items, next_padding_size, @@ -1146,10 +1145,8 @@ fn updateDeclDebugInfoAllocation(self: *Dwarf, file: *File, atom: *Atom, len: u3 }, .wasm => { const wasm_file = file.cast(File.Wasm).?; - const segment_index = try wasm_file.getDebugInfoIndex(); - const segment = &wasm_file.segments.items[segment_index]; - const offset = segment.offset + atom.off; - try writeDbgInfoNopsToArrayList(gpa, &wasm_file.debug_info, offset, 0, &.{0}, atom.len, false); + const debug_info = &wasm_file.debug_info_atom.?.code; + try writeDbgInfoNopsToArrayList(gpa, debug_info, atom.off, 0, &.{0}, atom.len, false); }, else => unreachable, } @@ -1276,27 +1273,25 @@ fn writeDeclDebugInfo(self: *Dwarf, file: *File, atom: *Atom, dbg_info_buf: []co }, .wasm => { const wasm_file = file.cast(File.Wasm).?; - const segment_index = try wasm_file.getDebugInfoIndex(); - const segment = &wasm_file.segments.items[segment_index]; - const debug_info = &wasm_file.debug_info; - if (needed_size != segment.size) { + const info_atom = wasm_file.debug_info_atom.?; + const debug_info = &info_atom.code; + const segment_size = debug_info.items.len; + if (needed_size != segment_size) { log.debug(" needed size does not equal allocated size: {d}", .{needed_size}); - if (needed_size > segment.size) { - log.debug(" allocating {d} bytes for 'debug info' information", .{needed_size - segment.size}); + if (needed_size > segment_size) { + log.debug(" allocating {d} bytes for 'debug info' information", .{needed_size - segment_size}); try debug_info.resize(self.allocator, needed_size); - mem.set(u8, debug_info.items[segment.size..], 0); + mem.set(u8, debug_info.items[segment_size..], 0); } - segment.size = needed_size; debug_info.items.len = needed_size; } - const offset = segment.offset + atom.off; log.debug(" writeDbgInfoNopsToArrayList debug_info_len={d} offset={d} content_len={d} next_padding_size={d}", .{ - debug_info.items.len, offset, dbg_info_buf.len, next_padding_size, + debug_info.items.len, atom.off, dbg_info_buf.len, next_padding_size, }); try writeDbgInfoNopsToArrayList( gpa, debug_info, - offset, + atom.off, prev_padding_size, dbg_info_buf, next_padding_size, @@ -1337,10 +1332,9 @@ pub fn updateDeclLineNumber(self: *Dwarf, file: *File, decl: *const Module.Decl) }, .wasm => { const wasm_file = file.cast(File.Wasm).?; - const segment_index = wasm_file.getDebugLineIndex() catch unreachable; - const segment = wasm_file.segments.items[segment_index]; - const offset = segment.offset + decl.fn_link.wasm.src_fn.off + self.getRelocDbgLineOff(); - mem.copy(u8, wasm_file.debug_line.items[offset..], &data); + const offset = decl.fn_link.wasm.src_fn.off + self.getRelocDbgLineOff(); + const atom = wasm_file.debug_line_atom.?; + mem.copy(u8, atom.code.items[offset..], &data); }, else => unreachable, } @@ -1576,8 +1570,9 @@ pub fn writeDbgAbbrev(self: *Dwarf, file: *File) !void { }, .wasm => { const wasm_file = file.cast(File.Wasm).?; - try wasm_file.debug_abbrev.resize(wasm_file.base.allocator, needed_size); - mem.copy(u8, wasm_file.debug_abbrev.items, &abbrev_buf); + const debug_abbrev = &wasm_file.debug_abbrev_atom.?.code; + try debug_abbrev.resize(wasm_file.base.allocator, needed_size); + mem.copy(u8, debug_abbrev.items, &abbrev_buf); }, else => unreachable, } @@ -1687,7 +1682,8 @@ pub fn writeDbgInfoHeader(self: *Dwarf, file: *File, module: *Module, low_pc: u6 }, .wasm => { const wasm_file = file.cast(File.Wasm).?; - try writeDbgInfoNopsToArrayList(self.allocator, &wasm_file.debug_info, 0, 0, di_buf.items, jmp_amt, false); + const debug_info = &wasm_file.debug_info_atom.?.code; + try writeDbgInfoNopsToArrayList(self.allocator, debug_info, 0, 0, di_buf.items, jmp_amt, false); }, else => unreachable, } @@ -2016,8 +2012,9 @@ pub fn writeDbgAranges(self: *Dwarf, file: *File, addr: u64, size: u64) !void { }, .wasm => { const wasm_file = file.cast(File.Wasm).?; - try wasm_file.debug_aranges.resize(wasm_file.base.allocator, needed_size); - mem.copy(u8, wasm_file.debug_aranges.items, di_buf.items); + const debug_ranges = &wasm_file.debug_ranges_atom.?.code; + try debug_ranges.resize(wasm_file.base.allocator, needed_size); + mem.copy(u8, debug_ranges.items, di_buf.items); }, else => unreachable, } @@ -2139,7 +2136,8 @@ pub fn writeDbgLineHeader(self: *Dwarf, file: *File, module: *Module) !void { }, .wasm => { const wasm_file = file.cast(File.Wasm).?; - writeDbgLineNopsBuffered(wasm_file.debug_line.items, 0, 0, di_buf.items, jmp_amt); + const debug_line = wasm_file.debug_line_atom.?.code; + writeDbgLineNopsBuffered(debug_line.items, 0, 0, di_buf.items, jmp_amt); }, else => unreachable, } @@ -2287,7 +2285,8 @@ pub fn flushModule(self: *Dwarf, file: *File, module: *Module) !void { }, .wasm => { const wasm_file = file.cast(File.Wasm).?; - mem.copy(u8, wasm_file.debug_info.items[reloc.atom.off + reloc.offset ..], &buf); + const debug_info = wasm_file.debug_info_atom.?.code; + mem.copy(u8, debug_info.items[reloc.atom.off + reloc.offset ..], &buf); }, else => unreachable, } diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 050d9287a5..0c5f0e810f 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -67,6 +67,18 @@ code_section_index: ?u32 = null, debug_info_index: ?u32 = null, /// The index of the segment representing the custom '.debug_line' section. debug_line_index: ?u32 = null, +/// The index of the segment representing the custom '.debug_loc' section. +debug_loc_index: ?u32 = null, +/// The index of the segment representing the custom '.debug_ranges' section. +debug_ranges_index: ?u32 = null, +/// The index of the segment representing the custom '.debug_pubnames' section. +debug_pubnames_index: ?u32 = null, +/// The index of the segment representing the custom '.debug_pubtypes' section. +debug_pubtypes_index: ?u32 = null, +/// The index of the segment representing the custom '.debug_pubtypes' section. +debug_str_index: ?u32 = null, +/// The index of the segment representing the custom '.debug_pubtypes' section. +debug_abbrev_index: ?u32 = null, /// The count of imported functions. This number will be appended /// to the function indexes as their index starts at the lowest non-extern function. imported_functions_count: u32 = 0, @@ -83,24 +95,15 @@ 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 dwarf: ?Dwarf = null, -// *debug information* // -/// Contains all bytes for the '.debug_info' section -debug_info: std.ArrayListUnmanaged(u8) = .{}, -/// Contains all bytes for the '.debug_line' section -debug_line: std.ArrayListUnmanaged(u8) = .{}, -/// Contains all bytes for the '.debug_abbrev' section -debug_abbrev: std.ArrayListUnmanaged(u8) = .{}, -/// Contains all bytes for the '.debug_ranges' section -debug_aranges: std.ArrayListUnmanaged(u8) = .{}, - // Output sections /// Output type section func_types: std.ArrayListUnmanaged(wasm.Type) = .{}, @@ -156,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, @@ -209,6 +225,18 @@ pub const SymbolLoc = struct { } return wasm_bin.string_table.get(wasm_bin.symbols.items[self.index].name); } + + /// From a given symbol location, returns the final location. + /// e.g. when a symbol was resolved and replaced by the symbol + /// in a different file, this will return said location. + /// If the symbol wasn't replaced by another, this will return + /// the given location itself. + pub fn finalLoc(self: SymbolLoc, wasm_bin: *const Wasm) SymbolLoc { + if (wasm_bin.discarded.get(self)) |new_loc| { + return new_loc.finalLoc(wasm_bin); + } + return self; + } }; /// Generic string table that duplicates strings @@ -335,6 +363,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option }; } + try wasm_bin.initDebugSections(); return wasm_bin; } @@ -363,6 +392,24 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Wasm { return self; } +/// Initializes symbols and atoms for the debug sections +/// Initialization is only done when compiling Zig code. +/// When Zig is invoked as a linker instead, the atoms +/// 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. + 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 { for (files) |path| { if (try self.parseObjectFile(path)) continue; @@ -644,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| { @@ -692,11 +739,6 @@ pub fn deinit(self: *Wasm) void { if (self.dwarf) |*dwarf| { dwarf.deinit(); } - - self.debug_info.deinit(gpa); - self.debug_line.deinit(gpa); - self.debug_abbrev.deinit(gpa); - self.debug_aranges.deinit(gpa); } pub fn allocateDeclIndexes(self: *Wasm, decl_index: Module.Decl.Index) !void { @@ -1337,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; @@ -1359,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; } @@ -1370,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| { @@ -1399,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); @@ -1753,7 +1822,7 @@ fn setupMemory(self: *Wasm) !void { /// From a given object's index and the index of the segment, returns the corresponding /// index of the segment within the final data section. When the segment does not yet /// exist, a new one will be initialized and appended. The new index will be returned in that case. -pub fn getMatchingSegment(self: *Wasm, object_index: u16, relocatable_index: u32) !u32 { +pub fn getMatchingSegment(self: *Wasm, object_index: u16, relocatable_index: u32) !?u32 { const object: Object = self.objects.items[object_index]; const relocatable_data = object.relocatable_data[relocatable_index]; const index = @intCast(u32, self.segments.items.len); @@ -1765,27 +1834,83 @@ pub fn getMatchingSegment(self: *Wasm, object_index: u16, relocatable_index: u32 const result = try self.data_segments.getOrPut(self.base.allocator, segment_info.outputName(merge_segment)); if (!result.found_existing) { result.value_ptr.* = index; - try self.segments.append(self.base.allocator, .{ - .alignment = 1, - .size = 0, - .offset = 0, - }); + try self.appendDummySegment(); return index; } else return result.value_ptr.*; }, .code => return self.code_section_index orelse blk: { self.code_section_index = index; - try self.segments.append(self.base.allocator, .{ - .alignment = 1, - .size = 0, - .offset = 0, - }); + try self.appendDummySegment(); break :blk index; }, - .custom => return error.@"TODO: Custom section relocations for wasm", + .debug => { + const debug_name = object.getDebugName(relocatable_data); + if (mem.eql(u8, debug_name, ".debug_info")) { + return self.debug_info_index orelse blk: { + self.debug_info_index = index; + try self.appendDummySegment(); + break :blk index; + }; + } else if (mem.eql(u8, debug_name, ".debug_line")) { + return self.debug_line_index orelse blk: { + self.debug_line_index = index; + try self.appendDummySegment(); + break :blk index; + }; + } else if (mem.eql(u8, debug_name, ".debug_loc")) { + return self.debug_loc_index orelse blk: { + self.debug_loc_index = index; + try self.appendDummySegment(); + break :blk index; + }; + } else if (mem.eql(u8, debug_name, ".debug_ranges")) { + return self.debug_line_index orelse blk: { + self.debug_ranges_index = index; + try self.appendDummySegment(); + break :blk index; + }; + } else if (mem.eql(u8, debug_name, ".debug_pubnames")) { + return self.debug_pubnames_index orelse blk: { + self.debug_pubnames_index = index; + try self.appendDummySegment(); + break :blk index; + }; + } else if (mem.eql(u8, debug_name, ".debug_pubtypes")) { + return self.debug_pubtypes_index orelse blk: { + self.debug_pubtypes_index = index; + try self.appendDummySegment(); + break :blk index; + }; + } else if (mem.eql(u8, debug_name, ".debug_abbrev")) { + return self.debug_abbrev_index orelse blk: { + self.debug_abbrev_index = index; + try self.appendDummySegment(); + break :blk index; + }; + } else if (mem.eql(u8, debug_name, ".debug_str")) { + return self.debug_str_index orelse blk: { + self.debug_str_index = index; + try self.appendDummySegment(); + break :blk index; + }; + } else { + log.warn("found unknown debug section '{s}'", .{debug_name}); + log.warn(" debug section will be skipped", .{}); + return null; + } + }, } } +/// Appends a new segment with default field values +fn appendDummySegment(self: *Wasm) !void { + try self.segments.append(self.base.allocator, .{ + .alignment = 1, + .size = 0, + .offset = 0, + }); +} + /// Returns the symbol index of the error name table. /// /// When the symbol does not yet exist, it will create a new one instead. @@ -1903,50 +2028,52 @@ fn populateErrorNameTable(self: *Wasm) !void { try self.parseAtom(names_atom, .{ .data = .read_only }); } -pub fn getDebugInfoIndex(self: *Wasm) !u32 { - assert(self.dwarf != null); - return self.debug_info_index orelse { - self.debug_info_index = @intCast(u32, self.segments.items.len); - const segment = try self.segments.addOne(self.base.allocator); - segment.* = .{ - .size = 0, - .offset = 0, - // debug sections always have alignment '1' - .alignment = 1, - }; - return self.debug_info_index.?; +/// 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, 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; }; -} - -pub fn getDebugLineIndex(self: *Wasm) !u32 { - assert(self.dwarf != null); - return self.debug_line_index orelse { - self.debug_line_index = @intCast(u32, self.segments.items.len); - const segment = try self.segments.addOne(self.base.allocator); - segment.* = .{ - .size = 0, - .offset = 0, - .alignment = 1, - }; - return self.debug_line_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.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); } - const mod = self.base.options.module.?; - var decl_it = self.decls.keyIterator(); - while (decl_it.next()) |decl_index_ptr| { - const decl = mod.declPtr(decl_index_ptr.*); - const atom = &decl.link.wasm; - atom.next = null; - atom.prev = null; + if (self.base.options.module) |mod| { + var decl_it = self.decls.keyIterator(); + while (decl_it.next()) |decl_index_ptr| { + const decl = mod.declPtr(decl_index_ptr.*); + const atom = &decl.link.wasm; + atom.next = null; + atom.prev = null; - for (atom.locals.items) |*local_atom| { - local_atom.next = null; - local_atom.prev = null; + for (atom.locals.items) |*local_atom| { + local_atom.next = null; + local_atom.prev = null; + } } } self.functions.clearRetainingCapacity(); @@ -1959,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 { @@ -2036,29 +2169,34 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod defer self.resetState(); try self.setupStart(); try self.setupImports(); - const mod = self.base.options.module.?; - var decl_it = self.decls.keyIterator(); - while (decl_it.next()) |decl_index_ptr| { - const decl = mod.declPtr(decl_index_ptr.*); - if (decl.isExtern()) continue; - const atom = &decl.*.link.wasm; - if (decl.ty.zigTypeTag() == .Fn) { - try self.parseAtom(atom, .{ .function = decl.fn_link.wasm }); - } else if (decl.getVariable()) |variable| { - if (!variable.is_mutable) { - try self.parseAtom(atom, .{ .data = .read_only }); - } else if (variable.init.isUndefDeep()) { - try self.parseAtom(atom, .{ .data = .uninitialized }); + if (self.base.options.module) |mod| { + var decl_it = self.decls.keyIterator(); + while (decl_it.next()) |decl_index_ptr| { + const decl = mod.declPtr(decl_index_ptr.*); + if (decl.isExtern()) continue; + const atom = &decl.*.link.wasm; + if (decl.ty.zigTypeTag() == .Fn) { + try self.parseAtom(atom, .{ .function = decl.fn_link.wasm }); + } else if (decl.getVariable()) |variable| { + if (!variable.is_mutable) { + try self.parseAtom(atom, .{ .data = .read_only }); + } else if (variable.init.isUndefDeep()) { + try self.parseAtom(atom, .{ .data = .uninitialized }); + } else { + try self.parseAtom(atom, .{ .data = .initialized }); + } } else { - try self.parseAtom(atom, .{ .data = .initialized }); + try self.parseAtom(atom, .{ .data = .read_only }); + } + + // also parse atoms for a decl's locals + for (atom.locals.items) |*local_atom| { + try self.parseAtom(local_atom, .{ .data = .read_only }); } - } else { - try self.parseAtom(atom, .{ .data = .read_only }); } - // also parse atoms for a decl's locals - for (atom.locals.items) |*local_atom| { - try self.parseAtom(local_atom, .{ .data = .read_only }); + if (self.dwarf) |*dwarf| { + try dwarf.flushModule(&self.base, self.base.options.module.?); } } @@ -2066,9 +2204,6 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod try object.parseIntoAtoms(self.base.allocator, @intCast(u16, object_index), self); } - if (self.dwarf) |*dwarf| { - try dwarf.flushModule(&self.base, self.base.options.module.?); - } try self.allocateAtoms(); try self.setupMemory(); self.mapFunctionTable(); @@ -2424,19 +2559,44 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod } } else if (!self.base.options.strip) { if (self.dwarf) |*dwarf| { - if (self.debug_info_index != null) { - try dwarf.writeDbgAbbrev(&self.base); - // for debug info and ranges, the address is always 0, - // as locations are always offsets relative to 'code' section. - try dwarf.writeDbgInfoHeader(&self.base, mod, 0, code_section_size); - try dwarf.writeDbgAranges(&self.base, 0, code_section_size); - try dwarf.writeDbgLineHeader(&self.base, mod); - - try emitDebugSection(file, self.debug_info.items, ".debug_info"); - try emitDebugSection(file, self.debug_aranges.items, ".debug_ranges"); - try emitDebugSection(file, self.debug_abbrev.items, ".debug_abbrev"); - try emitDebugSection(file, self.debug_line.items, ".debug_line"); - try emitDebugSection(file, dwarf.strtab.items, ".debug_str"); + const mod = self.base.options.module.?; + try dwarf.writeDbgAbbrev(&self.base); + // for debug info and ranges, the address is always 0, + // as locations are always offsets relative to 'code' section. + try dwarf.writeDbgInfoHeader(&self.base, mod, 0, code_section_size); + try dwarf.writeDbgAranges(&self.base, 0, code_section_size); + try dwarf.writeDbgLineHeader(&self.base, mod); + } + + var debug_bytes = std.ArrayList(u8).init(self.base.allocator); + defer debug_bytes.deinit(); + + 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 }, + }; + + 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.name); + debug_bytes.clearRetainingCapacity(); } } try self.emitNameSection(file, arena); @@ -2444,6 +2604,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod } fn emitDebugSection(file: fs.File, data: []const u8, name: []const u8) !void { + if (data.len == 0) return; const header_offset = try reserveCustomSectionHeader(file); const writer = file.writer(); try leb.writeULEB128(writer, @intCast(u32, name.len)); @@ -3149,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), diff --git a/src/link/Wasm/Atom.zig b/src/link/Wasm/Atom.zig index 9e7f7a5a76..3e288fa018 100644 --- a/src/link/Wasm/Atom.zig +++ b/src/link/Wasm/Atom.zig @@ -90,6 +90,19 @@ pub fn getFirst(self: *Atom) *Atom { return tmp; } +/// Unlike `getFirst` this returns the first `*Atom` that was +/// produced from Zig code, rather than an object file. +/// This is useful for debug sections where we want to extend +/// the bytes, and don't want to overwrite existing Atoms. +pub fn getFirstZigAtom(self: *Atom) *Atom { + if (self.file == null) return self; + var tmp = self; + return while (tmp.prev) |prev| { + if (prev.file == null) break prev; + tmp = prev; + } else unreachable; // must allocate an Atom first! +} + /// Returns the location of the symbol that represents this `Atom` pub fn symbolLoc(self: Atom) Wasm.SymbolLoc { return .{ .file = self.file, .index = self.sym_index }; @@ -145,7 +158,7 @@ pub fn resolveRelocs(self: *Atom, wasm_bin: *const Wasm) void { /// All values will be represented as a `u64` as all values can fit within it. /// The final value must be casted to the correct size. fn relocationValue(self: Atom, relocation: types.Relocation, wasm_bin: *const Wasm) u64 { - const target_loc: Wasm.SymbolLoc = .{ .file = self.file, .index = relocation.index }; + const target_loc = (Wasm.SymbolLoc{ .file = self.file, .index = relocation.index }).finalLoc(wasm_bin); const symbol = target_loc.getSymbol(wasm_bin).*; switch (relocation.relocation_type) { .R_WASM_FUNCTION_INDEX_LEB => return symbol.index, @@ -174,19 +187,34 @@ fn relocationValue(self: Atom, relocation: types.Relocation, wasm_bin: *const Wa => { std.debug.assert(symbol.tag == .data and !symbol.isUndefined()); const merge_segment = wasm_bin.base.options.output_mode != .Obj; - const target_atom_loc = wasm_bin.discarded.get(target_loc) orelse target_loc; - const target_atom = wasm_bin.symbol_atom.get(target_atom_loc).?; + const target_atom = wasm_bin.symbol_atom.get(target_loc).?; const segment_info = if (target_atom.file) |object_index| blk: { break :blk wasm_bin.objects.items[object_index].segment_info; - } else wasm_bin.segment_info.items; + } else wasm_bin.segment_info.values(); const segment_name = segment_info[symbol.index].outputName(merge_segment); const segment_index = wasm_bin.data_segments.get(segment_name).?; const segment = wasm_bin.segments.items[segment_index]; return target_atom.offset + segment.offset + (relocation.addend orelse 0); }, .R_WASM_EVENT_INDEX_LEB => return symbol.index, - .R_WASM_SECTION_OFFSET_I32, - .R_WASM_FUNCTION_OFFSET_I32, - => return relocation.offset, + .R_WASM_SECTION_OFFSET_I32 => { + const target_atom = wasm_bin.symbol_atom.get(target_loc).?; + return target_atom.offset + (relocation.addend orelse 0); + }, + .R_WASM_FUNCTION_OFFSET_I32 => { + const target_atom = wasm_bin.symbol_atom.get(target_loc).?; + var atom = target_atom.getFirst(); + var offset: u32 = 0; + // TODO: Calculate this during atom allocation, rather than + // this linear calculation. For now it's done here as atoms + // are being sorted after atom allocation, as functions aren't + // merged until later. + while (true) { + offset += 5; // each atom uses 5 bytes to store its body's size + if (atom == target_atom) break; + atom = atom.next.?; + } + return target_atom.offset + offset + (relocation.addend orelse 0); + }, } } diff --git a/src/link/Wasm/Object.zig b/src/link/Wasm/Object.zig index 50827ca9fb..42c3b8a1a0 100644 --- a/src/link/Wasm/Object.zig +++ b/src/link/Wasm/Object.zig @@ -63,16 +63,21 @@ relocatable_data: []const RelocatableData = &.{}, /// import name, module name and export names. Each string will be deduplicated /// and returns an offset into the table. string_table: Wasm.StringTable = .{}, +/// All the names of each debug section found in the current object file. +/// Each name is terminated by a null-terminator. The name can be found, +/// from the `index` offset within the `RelocatableData`. +debug_names: [:0]const u8, /// Represents a single item within a section (depending on its `type`) const RelocatableData = struct { /// The type of the relocatable data - type: enum { data, code, custom }, + type: enum { data, code, debug }, /// Pointer to the data of the segment, where its length is written to `size` data: [*]u8, /// The size in bytes of the data representing the segment within the section size: u32, - /// The index within the section itself + /// The index within the section itself, or in case of a debug section, + /// the offset within the `string_table`. index: u32, /// The offset within the section where the data starts offset: u32, @@ -96,9 +101,16 @@ const RelocatableData = struct { return switch (self.type) { .data => .data, .code => .function, - .custom => .section, + .debug => .section, }; } + + /// Returns the index within a section itself, or in case of a debug section, + /// returns the section index within the object file. + pub fn getIndex(self: RelocatableData) u32 { + if (self.type == .debug) return self.section_index; + return self.index; + } }; pub const InitError = error{NotObjectFile} || ParseError || std.fs.File.ReadError; @@ -111,6 +123,7 @@ pub fn create(gpa: Allocator, file: std.fs.File, name: []const u8, maybe_max_siz var object: Object = .{ .file = file, .name = try gpa.dupe(u8, name), + .debug_names = &.{}, }; var is_object_file: bool = false; @@ -197,6 +210,11 @@ pub fn importedCountByKind(self: *const Object, kind: std.wasm.ExternalKind) u32 } else i; } +/// From a given `RelocatableDate`, find the corresponding debug section name +pub fn getDebugName(self: *const Object, relocatable_data: RelocatableData) []const u8 { + return self.string_table.get(relocatable_data.index); +} + /// Checks if the object file is an MVP version. /// When that's the case, we check if there's an import table definiton with its name /// set to '__indirect_function_table". When that's also the case, @@ -328,10 +346,15 @@ fn Parser(comptime ReaderType: type) type { self.object.version = version; var relocatable_data = std.ArrayList(RelocatableData).init(gpa); - - errdefer while (relocatable_data.popOrNull()) |rel_data| { - gpa.free(rel_data.data[0..rel_data.size]); - } else relocatable_data.deinit(); + var debug_names = std.ArrayList(u8).init(gpa); + + errdefer { + while (relocatable_data.popOrNull()) |rel_data| { + gpa.free(rel_data.data[0..rel_data.size]); + } else relocatable_data.deinit(); + gpa.free(debug_names.items); + debug_names.deinit(); + } var section_index: u32 = 0; while (self.reader.reader().readByte()) |byte| : (section_index += 1) { @@ -347,11 +370,26 @@ fn Parser(comptime ReaderType: type) type { if (std.mem.eql(u8, name, "linking")) { is_object_file.* = true; + self.object.relocatable_data = relocatable_data.items; // at this point no new relocatable sections will appear so we're free to store them. try self.parseMetadata(gpa, @intCast(usize, reader.context.bytes_left)); } else if (std.mem.startsWith(u8, name, "reloc")) { try self.parseRelocations(gpa); } else if (std.mem.eql(u8, name, "target_features")) { try self.parseFeatures(gpa); + } else if (std.mem.startsWith(u8, name, ".debug")) { + const debug_size = @intCast(u32, reader.context.bytes_left); + const debug_content = try gpa.alloc(u8, debug_size); + errdefer gpa.free(debug_content); + try reader.readNoEof(debug_content); + + try relocatable_data.append(.{ + .type = .debug, + .data = debug_content.ptr, + .size = debug_size, + .index = try self.object.string_table.put(gpa, name), + .offset = 0, // debug sections only contain 1 entry, so no need to calculate offset + .section_index = section_index, + }); } else { try reader.skipBytes(reader.context.bytes_left, .{}); } @@ -737,7 +775,12 @@ fn Parser(comptime ReaderType: type) type { }, .section => { symbol.index = try leb.readULEB128(u32, reader); - symbol.name = try self.object.string_table.put(gpa, @tagName(symbol.tag)); + for (self.object.relocatable_data) |data| { + if (data.section_index == symbol.index) { + symbol.name = data.index; + break; + } + } }, else => { symbol.index = try leb.readULEB128(u32, reader); @@ -827,7 +870,6 @@ fn assertEnd(reader: anytype) !void { /// Parses an object file into atoms, for code and data sections pub fn parseIntoAtoms(self: *Object, gpa: Allocator, object_index: u16, wasm_bin: *Wasm) !void { - log.debug("Parsing data section into atoms", .{}); const Key = struct { kind: Symbol.Tag, index: u32, @@ -839,7 +881,7 @@ pub fn parseIntoAtoms(self: *Object, gpa: Allocator, object_index: u16, wasm_bin for (self.symtable) |symbol, symbol_index| { switch (symbol.tag) { - .function, .data => if (!symbol.isUndefined()) { + .function, .data, .section => if (!symbol.isUndefined()) { const gop = try symbol_for_segment.getOrPut(.{ .kind = symbol.tag, .index = symbol.index }); const sym_idx = @intCast(u32, symbol_index); if (!gop.found_existing) { @@ -852,12 +894,9 @@ pub fn parseIntoAtoms(self: *Object, gpa: Allocator, object_index: u16, wasm_bin } for (self.relocatable_data) |relocatable_data, index| { - const symbols = symbol_for_segment.getPtr(.{ - .kind = relocatable_data.getSymbolKind(), - .index = @intCast(u32, relocatable_data.index), - }) orelse continue; // encountered a segment we do not create an atom for - const sym_index = symbols.pop(); - const final_index = try wasm_bin.getMatchingSegment(object_index, @intCast(u32, index)); + const final_index = (try wasm_bin.getMatchingSegment(object_index, @intCast(u32, index))) orelse { + continue; // found unknown section, so skip parsing into atom as we do not know how to handle it. + }; const atom = try gpa.create(Atom); atom.* = Atom.empty; @@ -870,7 +909,6 @@ pub fn parseIntoAtoms(self: *Object, gpa: Allocator, object_index: u16, wasm_bin atom.file = object_index; atom.size = relocatable_data.size; atom.alignment = relocatable_data.getAlignment(self); - atom.sym_index = sym_index; const relocations: []types.Relocation = self.relocations.get(relocatable_data.section_index) orelse &.{}; for (relocations) |relocation| { @@ -892,28 +930,31 @@ pub fn parseIntoAtoms(self: *Object, gpa: Allocator, object_index: u16, wasm_bin try atom.code.appendSlice(gpa, relocatable_data.data[0..relocatable_data.size]); - // symbols referencing the same atom will be added as alias - // or as 'parent' when they are global. - while (symbols.popOrNull()) |idx| { - const alias_symbol = self.symtable[idx]; - const symbol = self.symtable[atom.sym_index]; - if (alias_symbol.isGlobal() and symbol.isLocal()) { - atom.sym_index = idx; + if (symbol_for_segment.getPtr(.{ + .kind = relocatable_data.getSymbolKind(), + .index = relocatable_data.getIndex(), + })) |symbols| { + atom.sym_index = symbols.pop(); + + // symbols referencing the same atom will be added as alias + // or as 'parent' when they are global. + while (symbols.popOrNull()) |idx| { + const alias_symbol = self.symtable[idx]; + const symbol = self.symtable[atom.sym_index]; + if (alias_symbol.isGlobal() and symbol.isLocal()) { + atom.sym_index = idx; + } } + try wasm_bin.symbol_atom.putNoClobber(gpa, atom.symbolLoc(), atom); } - try wasm_bin.symbol_atom.putNoClobber(gpa, atom.symbolLoc(), atom); const segment: *Wasm.Segment = &wasm_bin.segments.items[final_index]; - segment.alignment = std.math.max(segment.alignment, atom.alignment); - - if (wasm_bin.atoms.getPtr(final_index)) |last| { - last.*.next = atom; - atom.prev = last.*; - last.* = atom; - } else { - try wasm_bin.atoms.putNoClobber(gpa, final_index, atom); + if (relocatable_data.type == .data) { //code section and debug sections are 1-byte aligned + segment.alignment = std.math.max(segment.alignment, atom.alignment); } - log.debug("Parsed into atom: '{s}'", .{self.string_table.get(self.symtable[atom.sym_index].name)}); + + try wasm_bin.appendAtomAtIndex(final_index, atom); + log.debug("Parsed into atom: '{s}' at segment index {d}", .{ self.string_table.get(self.symtable[atom.sym_index].name), final_index }); } } |
