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) = .{},...@@ -103,8 +103,10 @@ debug_aranges: std.ArrayListUnmanaged(u8) = .{},
103// Output sections103// Output sections
104/// Output type section104/// Output type section
105func_types: std.ArrayListUnmanaged(wasm.Type) = .{},105func_types: std.ArrayListUnmanaged(wasm.Type) = .{},
106/// Output function section106/// Output function section where the key is the original
107functions: std.ArrayListUnmanaged(wasm.Func) = .{},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) = .{},
108/// Output global section110/// Output global section
109wasm_globals: std.ArrayListUnmanaged(wasm.Global) = .{},111wasm_globals: std.ArrayListUnmanaged(wasm.Global) = .{},
110/// Memory section112/// Memory section
...@@ -1042,8 +1044,12 @@ fn parseAtom(self: *Wasm, atom: *Atom, kind: Kind) !void {...@@ -1042,8 +1044,12 @@ fn parseAtom(self: *Wasm, atom: *Atom, kind: Kind) !void {
1042 const symbol = (SymbolLoc{ .file = null, .index = atom.sym_index }).getSymbol(self);1044 const symbol = (SymbolLoc{ .file = null, .index = atom.sym_index }).getSymbol(self);
1043 const final_index: u32 = switch (kind) {1045 const final_index: u32 = switch (kind) {
1044 .function => |fn_data| result: {1046 .function => |fn_data| result: {
1045 const index = @intCast(u32, self.functions.items.len + self.imported_functions_count);1047 const index = @intCast(u32, self.functions.count() + self.imported_functions_count);
1046 try self.functions.append(self.base.allocator, .{ .type_index = fn_data.type_index });1048 try self.functions.putNoClobber(
1049 self.base.allocator,
1050 .{ .file = null, .index = index },
1051 .{ .type_index = fn_data.type_index },
1052 );
1047 symbol.tag = .function;1053 symbol.tag = .function;
1048 symbol.index = index;1054 symbol.index = index;
10491055
...@@ -1256,8 +1262,14 @@ fn mergeSections(self: *Wasm) !void {...@@ -1256,8 +1262,14 @@ fn mergeSections(self: *Wasm) !void {
1256 switch (symbol.tag) {1262 switch (symbol.tag) {
1257 .function => {1263 .function => {
1258 const original_func = object.functions[index];1264 const original_func = object.functions[index];
1259 symbol.index = @intCast(u32, self.functions.items.len) + self.imported_functions_count;1265 const gop = try self.functions.getOrPut(
1260 try self.functions.append(self.base.allocator, original_func);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;
1261 },1273 },
1262 .global => {1274 .global => {
1263 const original_global = object.globals[index];1275 const original_global = object.globals[index];
...@@ -1273,7 +1285,7 @@ fn mergeSections(self: *Wasm) !void {...@@ -1273,7 +1285,7 @@ fn mergeSections(self: *Wasm) !void {
1273 }1285 }
1274 }1286 }
12751287
1276 log.debug("Merged ({d}) functions", .{self.functions.items.len});1288 log.debug("Merged ({d}) functions", .{self.functions.count()});
1277 log.debug("Merged ({d}) globals", .{self.wasm_globals.items.len});1289 log.debug("Merged ({d}) globals", .{self.wasm_globals.items.len});
1278 log.debug("Merged ({d}) tables", .{self.tables.items.len});1290 log.debug("Merged ({d}) tables", .{self.tables.items.len});
1279}1291}
...@@ -1282,6 +1294,13 @@ fn mergeSections(self: *Wasm) !void {...@@ -1282,6 +1294,13 @@ fn mergeSections(self: *Wasm) !void {
1282/// 'types' section, while assigning the type index to the representing1294/// 'types' section, while assigning the type index to the representing
1283/// section (import, export, function).1295/// section (import, export, function).
1284fn mergeTypes(self: *Wasm) !void {1296fn 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
1285 for (self.resolved_symbols.keys()) |sym_loc| {1304 for (self.resolved_symbols.keys()) |sym_loc| {
1286 if (sym_loc.file == null) {1305 if (sym_loc.file == null) {
1287 // zig code-generated symbols are already present in final type section1306 // zig code-generated symbols are already present in final type section
...@@ -1294,6 +1313,10 @@ fn mergeTypes(self: *Wasm) !void {...@@ -1294,6 +1313,10 @@ fn mergeTypes(self: *Wasm) !void {
1294 continue;1313 continue;
1295 }1314 }
12961315
1316 if (dirty.contains(symbol.index)) {
1317 continue; // We already added the type of this symbol
1318 }
1319
1297 if (symbol.isUndefined()) {1320 if (symbol.isUndefined()) {
1298 log.debug("Adding type from extern function '{s}'", .{sym_loc.getName(self)});1321 log.debug("Adding type from extern function '{s}'", .{sym_loc.getName(self)});
1299 const import: *types.Import = self.imports.getPtr(sym_loc).?;1322 const import: *types.Import = self.imports.getPtr(sym_loc).?;
...@@ -1301,9 +1324,11 @@ fn mergeTypes(self: *Wasm) !void {...@@ -1301,9 +1324,11 @@ fn mergeTypes(self: *Wasm) !void {
1301 import.kind.function = try self.putOrGetFuncType(original_type);1324 import.kind.function = try self.putOrGetFuncType(original_type);
1302 } else {1325 } else {
1303 log.debug("Adding type from function '{s}'", .{sym_loc.getName(self)});1326 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];
1305 func.type_index = try self.putOrGetFuncType(object.func_types[func.type_index]);1328 func.type_index = try self.putOrGetFuncType(object.func_types[func.type_index]);
1306 }1329 }
1330
1331 dirty.putAssumeCapacityNoClobber(symbol.index, {});
1307 }1332 }
1308 log.debug("Completed merging and deduplicating types. Total count: ({d})", .{self.func_types.items.len});1333 log.debug("Completed merging and deduplicating types. Total count: ({d})", .{self.func_types.items.len});
1309}1334}
...@@ -1711,7 +1736,11 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -1711,7 +1736,11 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
1711 for (comp.c_object_table.keys()) |c_object| {1736 for (comp.c_object_table.keys()) |c_object| {
1712 try positionals.append(c_object.status.success.object_path);1737 try positionals.append(c_object.status.success.object_path);
1713 }1738 }
1714 // TODO: Also link with other objects such as compiler-rt1739
1740 if (comp.compiler_rt_static_lib) |lib| {
1741 try positionals.append(lib.full_object_path);
1742 }
1743
1715 try self.parseInputFiles(positionals.items);1744 try self.parseInputFiles(positionals.items);
17161745
1717 var object_index: u16 = 0;1746 var object_index: u16 = 0;
...@@ -1840,10 +1869,10 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -1840,10 +1869,10 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
1840 }1869 }
18411870
1842 // Function section1871 // Function section
1843 if (self.functions.items.len != 0) {1872 if (self.functions.count() != 0) {
1844 const header_offset = try reserveVecSectionHeader(file);1873 const header_offset = try reserveVecSectionHeader(file);
1845 const writer = file.writer();1874 const writer = file.writer();
1846 for (self.functions.items) |function| {1875 for (self.functions.values()) |function| {
1847 try leb.writeULEB128(writer, function.type_index);1876 try leb.writeULEB128(writer, function.type_index);
1848 }1877 }
18491878
...@@ -1852,7 +1881,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -1852,7 +1881,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
1852 header_offset,1881 header_offset,
1853 .function,1882 .function,
1854 @intCast(u32, (try file.getPos()) - header_offset - header_size),1883 @intCast(u32, (try file.getPos()) - header_offset - header_size),
1855 @intCast(u32, self.functions.items.len),1884 @intCast(u32, self.functions.count()),
1856 );1885 );
1857 section_count += 1;1886 section_count += 1;
1858 }1887 }
...@@ -1984,22 +2013,41 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -1984,22 +2013,41 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
1984 const header_offset = try reserveVecSectionHeader(file);2013 const header_offset = try reserveVecSectionHeader(file);
1985 const writer = file.writer();2014 const writer = file.writer();
1986 var atom: *Atom = self.atoms.get(code_index).?.getFirst();2015 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
1987 while (true) {2021 while (true) {
1988 if (!is_obj) {2022 if (!is_obj) {
1989 try atom.resolveRelocs(self);2023 try atom.resolveRelocs(self);
1990 }2024 }
1991 try leb.writeULEB128(writer, atom.size);2025 sorted_atoms.appendAssumeCapacity(atom);
1992 try writer.writeAll(atom.code.items);
1993 atom = atom.next orelse break;2026 atom = atom.next orelse break;
1994 }2027 }
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
1996 code_section_size = @intCast(u32, (try file.getPos()) - header_offset - header_size);2044 code_section_size = @intCast(u32, (try file.getPos()) - header_offset - header_size);
1997 try writeVecSectionHeader(2045 try writeVecSectionHeader(
1998 file,2046 file,
1999 header_offset,2047 header_offset,
2000 .code,2048 .code,
2001 code_section_size,2049 code_section_size,
2002 @intCast(u32, self.functions.items.len),2050 @intCast(u32, self.functions.count()),
2003 );2051 );
2004 code_section_index = section_count;2052 code_section_index = section_count;
2005 section_count += 1;2053 section_count += 1;
...@@ -2135,7 +2183,7 @@ fn emitNameSection(self: *Wasm, file: fs.File, arena: Allocator) !void {...@@ -2135,7 +2183,7 @@ fn emitNameSection(self: *Wasm, file: fs.File, arena: Allocator) !void {
2135 }2183 }
2136 };2184 };
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);
2139 var globals = try std.ArrayList(Name).initCapacity(arena, self.wasm_globals.items.len + self.imported_globals_count);2187 var globals = try std.ArrayList(Name).initCapacity(arena, self.wasm_globals.items.len + self.imported_globals_count);
2140 var segments = try std.ArrayList(Name).initCapacity(arena, self.data_segments.count());2188 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 {...@@ -2145,7 +2193,7 @@ fn emitNameSection(self: *Wasm, file: fs.File, arena: Allocator) !void {
2145 break :blk self.string_table.get(self.imports.get(sym_loc).?.name);2193 break :blk self.string_table.get(self.imports.get(sym_loc).?.name);
2146 } else sym_loc.getName(self);2194 } else sym_loc.getName(self);
2147 switch (symbol.tag) {2195 switch (symbol.tag) {
2148 .function => funcs.appendAssumeCapacity(.{ .index = symbol.index, .name = name }),2196 .function => try funcs.append(.{ .index = symbol.index, .name = name }),
2149 .global => globals.appendAssumeCapacity(.{ .index = symbol.index, .name = name }),2197 .global => globals.appendAssumeCapacity(.{ .index = symbol.index, .name = name }),
2150 else => {},2198 else => {},
2151 }2199 }
src/link/Wasm/Atom.zig+1-1
...@@ -155,7 +155,7 @@ fn relocationValue(self: Atom, relocation: types.Relocation, wasm_bin: *const Wa...@@ -155,7 +155,7 @@ fn relocationValue(self: Atom, relocation: types.Relocation, wasm_bin: *const Wa
155 .R_WASM_TABLE_INDEX_SLEB,155 .R_WASM_TABLE_INDEX_SLEB,
156 .R_WASM_TABLE_INDEX_SLEB64,156 .R_WASM_TABLE_INDEX_SLEB64,
157 => return wasm_bin.function_table.get(target_loc) orelse 0,157 => 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,
159 .R_WASM_GLOBAL_INDEX_I32,159 .R_WASM_GLOBAL_INDEX_I32,
160 .R_WASM_GLOBAL_INDEX_LEB,160 .R_WASM_GLOBAL_INDEX_LEB,
161 => return symbol.index,161 => return symbol.index,