authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-05-22 19:07:16+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-06-24 08:12:17+02:00
log4d3715d89f97f3f9b6e366bbabe01f9d64ed56cf
tree593ec4b37a1c8f1e1a626076144aa310f7083aa4
parent8d03e4fc6b361e6cf96865acc05820556ae33863

wasm-linker: de-duplicate functions+atom sorting

Multiple symbols can point to the same function, this means that when we loop over the symbol list, we must deduplicate those functions being added twice. Additionaly, we must also ensure that when we append a new type and set the type index on a function, we must not do this again for the same function. This commit also implements sorting of code atoms to ensure their order matches the order of the function section to ensure the function signature matches that of the function body.

2 files changed, 66 insertions(+), 18 deletions(-)

src/link/Wasm.zig+65-17
......@@ -103,8 +103,10 @@ debug_aranges: std.ArrayListUnmanaged(u8) = .{},
103103// Output sections
104104/// Output type section
105105func_types: std.ArrayListUnmanaged(wasm.Type) = .{},
106/// Output function section
107functions: std.ArrayListUnmanaged(wasm.Func) = .{},
106/// Output function section where the key is the original
107/// function index and the value is function.
108/// This allows us to map multiple symbols to the same function.
109functions: std.AutoArrayHashMapUnmanaged(struct { file: ?u16, index: u32 }, wasm.Func) = .{},
108110/// Output global section
109111wasm_globals: std.ArrayListUnmanaged(wasm.Global) = .{},
110112/// Memory section
......@@ -1042,8 +1044,12 @@ fn parseAtom(self: *Wasm, atom: *Atom, kind: Kind) !void {
10421044 const symbol = (SymbolLoc{ .file = null, .index = atom.sym_index }).getSymbol(self);
10431045 const final_index: u32 = switch (kind) {
10441046 .function => |fn_data| result: {
1045 const index = @intCast(u32, self.functions.items.len + self.imported_functions_count);
1046 try self.functions.append(self.base.allocator, .{ .type_index = fn_data.type_index });
1047 const index = @intCast(u32, self.functions.count() + self.imported_functions_count);
1048 try self.functions.putNoClobber(
1049 self.base.allocator,
1050 .{ .file = null, .index = index },
1051 .{ .type_index = fn_data.type_index },
1052 );
10471053 symbol.tag = .function;
10481054 symbol.index = index;
10491055
......@@ -1256,8 +1262,14 @@ fn mergeSections(self: *Wasm) !void {
12561262 switch (symbol.tag) {
12571263 .function => {
12581264 const original_func = object.functions[index];
1259 symbol.index = @intCast(u32, self.functions.items.len) + self.imported_functions_count;
1260 try self.functions.append(self.base.allocator, original_func);
1265 const gop = try self.functions.getOrPut(
1266 self.base.allocator,
1267 .{ .file = sym_loc.file, .index = symbol.index },
1268 );
1269 if (!gop.found_existing) {
1270 gop.value_ptr.* = original_func;
1271 }
1272 symbol.index = @intCast(u32, gop.index) + self.imported_functions_count;
12611273 },
12621274 .global => {
12631275 const original_global = object.globals[index];
......@@ -1273,7 +1285,7 @@ fn mergeSections(self: *Wasm) !void {
12731285 }
12741286 }
12751287
1276 log.debug("Merged ({d}) functions", .{self.functions.items.len});
1288 log.debug("Merged ({d}) functions", .{self.functions.count()});
12771289 log.debug("Merged ({d}) globals", .{self.wasm_globals.items.len});
12781290 log.debug("Merged ({d}) tables", .{self.tables.items.len});
12791291}
......@@ -1282,6 +1294,13 @@ fn mergeSections(self: *Wasm) !void {
12821294/// 'types' section, while assigning the type index to the representing
12831295/// section (import, export, function).
12841296fn mergeTypes(self: *Wasm) !void {
1297 // A map to track which functions have already had their
1298 // type inserted. If we do this for the same function multiple times,
1299 // it will be overwritten with the incorrect type.
1300 var dirty = std.AutoHashMap(u32, void).init(self.base.allocator);
1301 try dirty.ensureUnusedCapacity(@intCast(u32, self.functions.count()) + self.imported_functions_count);
1302 defer dirty.deinit();
1303
12851304 for (self.resolved_symbols.keys()) |sym_loc| {
12861305 if (sym_loc.file == null) {
12871306 // zig code-generated symbols are already present in final type section
......@@ -1294,6 +1313,10 @@ fn mergeTypes(self: *Wasm) !void {
12941313 continue;
12951314 }
12961315
1316 if (dirty.contains(symbol.index)) {
1317 continue; // We already added the type of this symbol
1318 }
1319
12971320 if (symbol.isUndefined()) {
12981321 log.debug("Adding type from extern function '{s}'", .{sym_loc.getName(self)});
12991322 const import: *types.Import = self.imports.getPtr(sym_loc).?;
......@@ -1301,9 +1324,11 @@ fn mergeTypes(self: *Wasm) !void {
13011324 import.kind.function = try self.putOrGetFuncType(original_type);
13021325 } else {
13031326 log.debug("Adding type from function '{s}'", .{sym_loc.getName(self)});
1304 const func = &self.functions.items[symbol.index - self.imported_functions_count];
1327 const func = &self.functions.values()[symbol.index - self.imported_functions_count];
13051328 func.type_index = try self.putOrGetFuncType(object.func_types[func.type_index]);
13061329 }
1330
1331 dirty.putAssumeCapacityNoClobber(symbol.index, {});
13071332 }
13081333 log.debug("Completed merging and deduplicating types. Total count: ({d})", .{self.func_types.items.len});
13091334}
......@@ -1711,7 +1736,11 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
17111736 for (comp.c_object_table.keys()) |c_object| {
17121737 try positionals.append(c_object.status.success.object_path);
17131738 }
1714 // TODO: Also link with other objects such as compiler-rt
1739
1740 if (comp.compiler_rt_static_lib) |lib| {
1741 try positionals.append(lib.full_object_path);
1742 }
1743
17151744 try self.parseInputFiles(positionals.items);
17161745
17171746 var object_index: u16 = 0;
......@@ -1840,10 +1869,10 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
18401869 }
18411870
18421871 // Function section
1843 if (self.functions.items.len != 0) {
1872 if (self.functions.count() != 0) {
18441873 const header_offset = try reserveVecSectionHeader(file);
18451874 const writer = file.writer();
1846 for (self.functions.items) |function| {
1875 for (self.functions.values()) |function| {
18471876 try leb.writeULEB128(writer, function.type_index);
18481877 }
18491878
......@@ -1852,7 +1881,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
18521881 header_offset,
18531882 .function,
18541883 @intCast(u32, (try file.getPos()) - header_offset - header_size),
1855 @intCast(u32, self.functions.items.len),
1884 @intCast(u32, self.functions.count()),
18561885 );
18571886 section_count += 1;
18581887 }
......@@ -1984,22 +2013,41 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
19842013 const header_offset = try reserveVecSectionHeader(file);
19852014 const writer = file.writer();
19862015 var atom: *Atom = self.atoms.get(code_index).?.getFirst();
2016
2017 // The code section must be sorted in line with the function order.
2018 var sorted_atoms = try std.ArrayList(*Atom).initCapacity(self.base.allocator, self.functions.count());
2019 defer sorted_atoms.deinit();
2020
19872021 while (true) {
19882022 if (!is_obj) {
19892023 try atom.resolveRelocs(self);
19902024 }
1991 try leb.writeULEB128(writer, atom.size);
1992 try writer.writeAll(atom.code.items);
2025 sorted_atoms.appendAssumeCapacity(atom);
19932026 atom = atom.next orelse break;
19942027 }
19952028
2029 const atom_sort_fn = struct {
2030 fn sort(ctx: *const Wasm, lhs: *const Atom, rhs: *const Atom) bool {
2031 const lhs_sym = lhs.symbolLoc().getSymbol(ctx);
2032 const rhs_sym = rhs.symbolLoc().getSymbol(ctx);
2033 return lhs_sym.index < rhs_sym.index;
2034 }
2035 }.sort;
2036
2037 std.sort.sort(*Atom, sorted_atoms.items, self, atom_sort_fn);
2038
2039 for (sorted_atoms.items) |sorted_atom| {
2040 try leb.writeULEB128(writer, sorted_atom.size);
2041 try writer.writeAll(sorted_atom.code.items);
2042 }
2043
19962044 code_section_size = @intCast(u32, (try file.getPos()) - header_offset - header_size);
19972045 try writeVecSectionHeader(
19982046 file,
19992047 header_offset,
20002048 .code,
20012049 code_section_size,
2002 @intCast(u32, self.functions.items.len),
2050 @intCast(u32, self.functions.count()),
20032051 );
20042052 code_section_index = section_count;
20052053 section_count += 1;
......@@ -2135,7 +2183,7 @@ fn emitNameSection(self: *Wasm, file: fs.File, arena: Allocator) !void {
21352183 }
21362184 };
21372185
2138 var funcs = try std.ArrayList(Name).initCapacity(arena, self.functions.items.len + self.imported_functions_count);
2186 var funcs = try std.ArrayList(Name).initCapacity(arena, self.functions.count() + self.imported_functions_count);
21392187 var globals = try std.ArrayList(Name).initCapacity(arena, self.wasm_globals.items.len + self.imported_globals_count);
21402188 var segments = try std.ArrayList(Name).initCapacity(arena, self.data_segments.count());
21412189
......@@ -2145,7 +2193,7 @@ fn emitNameSection(self: *Wasm, file: fs.File, arena: Allocator) !void {
21452193 break :blk self.string_table.get(self.imports.get(sym_loc).?.name);
21462194 } else sym_loc.getName(self);
21472195 switch (symbol.tag) {
2148 .function => funcs.appendAssumeCapacity(.{ .index = symbol.index, .name = name }),
2196 .function => try funcs.append(.{ .index = symbol.index, .name = name }),
21492197 .global => globals.appendAssumeCapacity(.{ .index = symbol.index, .name = name }),
21502198 else => {},
21512199 }
src/link/Wasm/Atom.zig+1-1
......@@ -155,7 +155,7 @@ fn relocationValue(self: Atom, relocation: types.Relocation, wasm_bin: *const Wa
155155 .R_WASM_TABLE_INDEX_SLEB,
156156 .R_WASM_TABLE_INDEX_SLEB64,
157157 => return wasm_bin.function_table.get(target_loc) orelse 0,
158 .R_WASM_TYPE_INDEX_LEB => return wasm_bin.functions.items[symbol.index].type_index,
158 .R_WASM_TYPE_INDEX_LEB => return wasm_bin.functions.values()[symbol.index - wasm_bin.imported_functions_count].type_index,
159159 .R_WASM_GLOBAL_INDEX_I32,
160160 .R_WASM_GLOBAL_INDEX_LEB,
161161 => return symbol.index,