wasm-linker: Fix symbol resolving and relocs

- Correctly get discard symbol by first checking if it was discarded or not.
- Remove imports if extern symbols were resolved by an object file.
- Correctly relocate data symbols by ensuring the atom is from the correct file.
- Fix the `Names` section by using the resolved symbols, rather than the ones defined in Zig code.
This commit is contained in:
Luuk de Gram 2022-02-16 22:13:25 +01:00
parent a4622501bd
commit 4ebe8a53ca
2 changed files with 59 additions and 19 deletions

View File

@ -109,7 +109,7 @@ globals: std.StringHashMapUnmanaged(SymbolLoc) = .{},
discarded: std.AutoHashMapUnmanaged(SymbolLoc, SymbolLoc) = .{},
/// List of all symbol locations which have been resolved by the linker and will be emit
/// into the final binary.
resolved_symbols: std.ArrayListUnmanaged(SymbolLoc) = .{},
resolved_symbols: std.AutoArrayHashMapUnmanaged(SymbolLoc, void) = .{},
pub const Segment = struct {
alignment: u32,
@ -134,10 +134,10 @@ pub const SymbolLoc = struct {
/// From a given location, returns the corresponding symbol in the wasm binary
pub fn getSymbol(self: SymbolLoc, wasm_bin: *const Wasm) *Symbol {
if (wasm_bin.discarded.get(self)) |new_loc| {
return new_loc.getSymbol(wasm_bin);
}
if (self.file) |object_index| {
if (wasm_bin.discarded.get(self)) |old_loc| {
return old_loc.getSymbol(wasm_bin);
}
const object = wasm_bin.objects.items[object_index];
return &object.symtable[self.index];
}
@ -245,7 +245,7 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void {
log.err(" symbol '{s}' defined in '{s}'", .{ symbol.name, object.name });
return error.undefinedLocal;
}
try self.resolved_symbols.append(self.base.allocator, location);
try self.resolved_symbols.putNoClobber(self.base.allocator, location, {});
continue;
}
@ -255,6 +255,7 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void {
const maybe_existing = try self.globals.getOrPut(self.base.allocator, sym_name);
if (!maybe_existing.found_existing) {
maybe_existing.value_ptr.* = location;
try self.resolved_symbols.putNoClobber(self.base.allocator, location, {});
continue;
}
@ -277,12 +278,14 @@ fn resolveSymbolsInObject(self: *Wasm, object_index: u16) !void {
}
// simply overwrite with the new symbol
log.info("Overwriting symbol '{s}'", .{symbol.name});
log.info(" old definition in '{s}'", .{existing_file_path});
log.info(" new definition in '{s}'", .{object.name});
log.debug("Overwriting symbol '{s}'", .{symbol.name});
log.debug(" old definition in '{s}'", .{existing_file_path});
log.debug(" new definition in '{s}'", .{object.name});
try self.discarded.putNoClobber(self.base.allocator, maybe_existing.value_ptr.*, location);
maybe_existing.value_ptr.* = location;
try self.globals.put(self.base.allocator, sym_name, location);
try self.resolved_symbols.put(self.base.allocator, location, {});
assert(self.resolved_symbols.swapRemove(existing_loc));
}
}
@ -360,6 +363,11 @@ pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void {
atom.sym_index = @intCast(u32, self.symbols.items.len);
self.symbols.appendAssumeCapacity(symbol);
}
try self.resolved_symbols.putNoClobber(self.base.allocator, .{
.index = atom.sym_index,
.file = null,
}, {});
}
pub fn updateFunc(self: *Wasm, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {
@ -454,7 +462,9 @@ fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, code: []const u8) !void {
const atom: *Atom = &decl.link.wasm;
atom.size = @intCast(u32, code.len);
atom.alignment = decl.ty.abiAlignment(self.base.options.target);
self.symbols.items[atom.sym_index].name = try self.base.allocator.dupeZ(u8, std.mem.sliceTo(decl.name, 0));
const symbol = &self.symbols.items[atom.sym_index];
symbol.name = try self.base.allocator.dupeZ(u8, std.mem.sliceTo(decl.name, 0));
symbol.setFlag(.WASM_SYM_BINDING_LOCAL);
try atom.code.appendSlice(self.base.allocator, code);
}
@ -566,7 +576,7 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
if (decl.isExtern()) {
assert(self.imports.remove(.{ .file = null, .index = atom.sym_index }));
}
assert(self.resolved_symbols.swapRemove(.{ .index = atom.sym_index, .file = null }));
atom.deinit(self.base.allocator);
}
@ -594,7 +604,7 @@ fn addOrUpdateImport(self: *Wasm, decl: *Module.Decl) !void {
symbol.name = try self.base.allocator.dupeZ(u8, decl_name);
symbol.setUndefined(true);
// also add it as a global so it can be resolved
try self.globals.put(self.base.allocator, decl_name, .{ .file = null, .index = symbol_index });
try self.globals.putNoClobber(self.base.allocator, decl_name, .{ .file = null, .index = symbol_index });
switch (decl.ty.zigTypeTag()) {
.Fn => {
const gop = try self.imports.getOrPut(self.base.allocator, .{ .index = symbol_index, .file = null });
@ -620,7 +630,7 @@ const Kind = union(enum) {
/// Parses an Atom and inserts its metadata into the corresponding sections.
fn parseAtom(self: *Wasm, atom: *Atom, kind: Kind) !void {
const symbol: *Symbol = &self.symbols.items[atom.sym_index];
const symbol = (SymbolLoc{ .file = null, .index = atom.sym_index }).getSymbol(self);
const final_index: u32 = switch (kind) {
.function => |fn_data| result: {
const index = @intCast(u32, self.functions.items.len + self.imported_functions_count);
@ -711,7 +721,19 @@ fn allocateAtoms(self: *Wasm) !void {
}
fn setupImports(self: *Wasm) !void {
for (self.resolved_symbols.items) |symbol_loc| {
log.debug("Merging imports", .{});
var discarded_it = self.discarded.keyIterator();
while (discarded_it.next()) |discarded| {
if (discarded.file == null) {
// remove an import if it was resolved
if (self.imports.remove(discarded.*)) {
log.debug("Removed symbol '{s}' as an import", .{
discarded.getSymbol(self).name,
});
}
}
}
for (self.resolved_symbols.keys()) |symbol_loc| {
if (symbol_loc.file == null) {
// imports generated by Zig code are already in the `import` section
continue;
@ -755,6 +777,12 @@ fn setupImports(self: *Wasm) !void {
self.imported_functions_count = function_index;
self.imported_globals_count = global_index;
self.imported_tables_count = table_index;
log.debug("Merged ({d}) functions, ({d}) globals, and ({d}) tables into import section", .{
function_index,
global_index,
table_index,
});
}
/// Takes the global, function and table section from each linked object file
@ -770,7 +798,7 @@ fn mergeSections(self: *Wasm) !void {
try self.tables.append(self.base.allocator, table);
}
for (self.resolved_symbols.items) |sym_loc| {
for (self.resolved_symbols.keys()) |sym_loc| {
if (sym_loc.file == null) {
// Zig code-generated symbols are already within the sections and do not
// require to be merged
@ -816,7 +844,7 @@ fn mergeSections(self: *Wasm) !void {
/// 'types' section, while assigning the type index to the representing
/// section (import, export, function).
fn mergeTypes(self: *Wasm) !void {
for (self.resolved_symbols.items) |sym_loc| {
for (self.resolved_symbols.keys()) |sym_loc| {
if (sym_loc.file == null) {
// zig code-generated symbols are already present in final type section
continue;
@ -850,7 +878,7 @@ fn setupExports(self: *Wasm) !void {
try self.exports.append(self.base.allocator, .{ .name = "memory", .kind = .memory, .index = 0 });
}
for (self.resolved_symbols.items) |sym_loc| {
for (self.resolved_symbols.keys()) |sym_loc| {
const symbol = sym_loc.getSymbol(self);
if (!symbol.isExported()) continue;
@ -1067,7 +1095,6 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
var object_index: u16 = 0;
while (object_index < self.objects.items.len) : (object_index += 1) {
try self.resolveSymbolsInObject(object_index);
try self.objects.items[object_index].parseIntoAtoms(self.base.allocator, object_index, self);
}
// When we finish/error we reset the state of the linker
@ -1090,6 +1117,11 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
}
}
while (object_index > 0) {
object_index -= 1;
try self.objects.items[object_index].parseIntoAtoms(self.base.allocator, object_index, self);
}
try self.setupMemory();
try self.allocateAtoms();
self.mapFunctionTable();
@ -1428,7 +1460,8 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
var segments = try std.ArrayList(Name).initCapacity(self.base.allocator, self.data_segments.count());
defer segments.deinit();
for (self.symbols.items) |symbol| {
for (self.resolved_symbols.keys()) |sym_loc| {
const symbol = sym_loc.getSymbol(self).*;
switch (symbol.tag) {
.function => funcs.appendAssumeCapacity(.{ .index = symbol.index, .name = mem.sliceTo(symbol.name, 0) }),
.global => globals.appendAssumeCapacity(.{ .index = symbol.index, .name = mem.sliceTo(symbol.name, 0) }),

View File

@ -97,6 +97,7 @@ pub fn symbolAtom(self: *Atom, symbol_index: u32) *Atom {
/// Resolves the relocations within the atom, writing the new value
/// at the calculated offset.
pub fn resolveRelocs(self: *Atom, wasm_bin: *const Wasm) !void {
if (self.relocs.items.len == 0) return;
const loc: Wasm.SymbolLoc = .{ .file = self.file, .index = self.sym_index };
const symbol = loc.getSymbol(wasm_bin).*;
log.debug("Resolving relocs in atom '{s}' count({d})", .{
@ -172,7 +173,13 @@ fn relocationValue(self: Atom, relocation: types.Relocation, wasm_bin: *const Wa
const atom_index = wasm_bin.data_segments.get(segment_name).?;
var target_atom = wasm_bin.atoms.getPtr(atom_index).?.*.getFirst();
while (true) {
if (target_atom.sym_index == relocation.index) break;
// TODO: Can we simplify this by providing the ability to find and atom
// based on a symbol location.
if (target_atom.sym_index == relocation.index) {
if (target_atom.file) |file| {
if (self.file != null and self.file.? == file) break;
} else if (self.file == null) break;
}
target_atom = target_atom.next orelse break;
}
const segment = wasm_bin.segments.items[atom_index];