Merge pull request #12770 from Luukdegram/wasm-dwarf

wasm-linker: implement linking debug-information
This commit is contained in:
Luuk de Gram 2022-09-08 16:57:23 +02:00 committed by GitHub
commit ab4b26d8a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 428 additions and 195 deletions

View File

@ -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;
}

View File

@ -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,
}

View File

@ -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;
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.?;
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.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);
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);
}
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");
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),

View File

@ -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);
},
}
}

View File

@ -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);
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();
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 });
}
}