authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-11-20 20:35:31+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-11-28 15:47:07+01:00
log6f7a9b31443debf3e6d2be645261372de1bc5877
tree4414529acf2871e3083cdc533c3c1e35bf5da175
parent8856ba75059f74a326d1f8d3af40a30c5a3ac1ed
signaturelock-open Commit is signed but in an unrecognized format.

wasm-linker: deduplicate aliased functions

When multiple symbols point to the same function, we ensure any other symbol other than the original will be discarded and point to the original instead. This prevents emitting the same function code more than once.

2 files changed, 45 insertions(+), 14 deletions(-)

src/link/Wasm.zig+41-14
......@@ -110,7 +110,7 @@ func_types: std.ArrayListUnmanaged(std.wasm.Type) = .{},
110110/// Output function section where the key is the original
111111/// function index and the value is function.
112112/// This allows us to map multiple symbols to the same function.
113functions: std.AutoArrayHashMapUnmanaged(struct { file: ?u16, index: u32 }, std.wasm.Func) = .{},
113functions: std.AutoArrayHashMapUnmanaged(struct { file: ?u16, index: u32 }, struct { func: std.wasm.Func, sym_index: u32 }) = .{},
114114/// Output global section
115115wasm_globals: std.ArrayListUnmanaged(std.wasm.Global) = .{},
116116/// Memory section
......@@ -1584,7 +1584,7 @@ fn getFunctionSignature(wasm: *const Wasm, loc: SymbolLoc) std.wasm.Type {
15841584 const ty_index = wasm.imports.get(loc).?.kind.function;
15851585 return wasm.func_types.items[ty_index];
15861586 }
1587 return wasm.func_types.items[wasm.functions.get(.{ .file = loc.file, .index = loc.index }).?.type_index];
1587 return wasm.func_types.items[wasm.functions.get(.{ .file = loc.file, .index = symbol.index }).?.func.type_index];
15881588}
15891589
15901590/// Lowers a constant typed value to a local symbol and atom.
......@@ -2141,7 +2141,7 @@ fn parseAtom(wasm: *Wasm, atom_index: Atom.Index, kind: Kind) !void {
21412141 try wasm.functions.putNoClobber(
21422142 wasm.base.allocator,
21432143 .{ .file = null, .index = index },
2144 .{ .type_index = type_index },
2144 .{ .func = .{ .type_index = type_index }, .sym_index = atom.sym_index },
21452145 );
21462146 symbol.tag = .function;
21472147 symbol.index = index;
......@@ -2274,7 +2274,14 @@ fn allocateAtoms(wasm: *Wasm) !void {
22742274 while (true) {
22752275 const atom = wasm.getAtomPtr(atom_index);
22762276 const symbol_loc = atom.symbolLoc();
2277 const sym = symbol_loc.getSymbol(wasm);
2277 // Ensure we get the original symbol, so we verify the correct symbol on whether
2278 // it is dead or not and ensure an atom is removed when dead.
2279 // This is required as we may have parsed aliases into atoms.
2280 const sym = if (symbol_loc.file) |object_index| sym: {
2281 const object = wasm.objects.items[object_index];
2282 break :sym object.symtable[symbol_loc.index];
2283 } else wasm.symbols.items[symbol_loc.index];
2284
22782285 if (sym.isDead()) {
22792286 // Dead symbols must be unlinked from the linked-list to prevent them
22802287 // from being emit into the binary.
......@@ -2477,7 +2484,7 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void {
24772484 // call constructors
24782485 for (wasm.init_funcs.items) |init_func_loc| {
24792486 const symbol = init_func_loc.getSymbol(wasm);
2480 const func = wasm.functions.values()[symbol.index - wasm.imported_functions_count];
2487 const func = wasm.functions.values()[symbol.index - wasm.imported_functions_count].func;
24812488 const ty = wasm.func_types.items[func.type_index];
24822489
24832490 // Call function by its function index
......@@ -2519,7 +2526,7 @@ fn createSyntheticFunction(
25192526 try wasm.functions.putNoClobber(
25202527 wasm.base.allocator,
25212528 .{ .file = null, .index = func_index },
2522 .{ .type_index = ty_index },
2529 .{ .func = .{ .type_index = ty_index }, .sym_index = loc.index },
25232530 );
25242531 symbol.index = func_index;
25252532
......@@ -2740,6 +2747,9 @@ fn setupImports(wasm: *Wasm) !void {
27402747/// Takes the global, function and table section from each linked object file
27412748/// and merges it into a single section for each.
27422749fn mergeSections(wasm: *Wasm) !void {
2750 var removed_duplicates = std.ArrayList(SymbolLoc).init(wasm.base.allocator);
2751 defer removed_duplicates.deinit();
2752
27432753 for (wasm.resolved_symbols.keys()) |sym_loc| {
27442754 if (sym_loc.file == null) {
27452755 // Zig code-generated symbols are already within the sections and do not
......@@ -2767,9 +2777,19 @@ fn mergeSections(wasm: *Wasm) !void {
27672777 wasm.base.allocator,
27682778 .{ .file = sym_loc.file, .index = symbol.index },
27692779 );
2770 if (!gop.found_existing) {
2771 gop.value_ptr.* = object.functions[index];
2780 if (gop.found_existing) {
2781 // We found an alias to the same function, discard this symbol in favor of
2782 // the original symbol and point the discard function to it. This ensures
2783 // we only emit a single function, instead of duplicates.
2784 try wasm.discarded.putNoClobber(
2785 wasm.base.allocator,
2786 sym_loc,
2787 .{ .file = gop.key_ptr.*.file, .index = gop.value_ptr.*.sym_index },
2788 );
2789 try removed_duplicates.append(sym_loc);
2790 continue;
27722791 }
2792 gop.value_ptr.* = .{ .func = object.functions[index], .sym_index = sym_loc.index };
27732793 symbol.index = @as(u32, @intCast(gop.index)) + wasm.imported_functions_count;
27742794 },
27752795 .global => {
......@@ -2786,6 +2806,12 @@ fn mergeSections(wasm: *Wasm) !void {
27862806 }
27872807 }
27882808
2809 // For any removed duplicates, remove them from the resolved symbols list
2810 for (removed_duplicates.items) |sym_loc| {
2811 assert(wasm.resolved_symbols.swapRemove(sym_loc));
2812 sym_loc.getSymbol(wasm).unmark();
2813 }
2814
27892815 log.debug("Merged ({d}) functions", .{wasm.functions.count()});
27902816 log.debug("Merged ({d}) globals", .{wasm.wasm_globals.items.len});
27912817 log.debug("Merged ({d}) tables", .{wasm.tables.items.len});
......@@ -2821,7 +2847,7 @@ fn mergeTypes(wasm: *Wasm) !void {
28212847 import.kind.function = try wasm.putOrGetFuncType(original_type);
28222848 } else if (!dirty.contains(symbol.index)) {
28232849 log.debug("Adding type from function '{s}'", .{sym_loc.getName(wasm)});
2824 const func = &wasm.functions.values()[symbol.index - wasm.imported_functions_count];
2850 const func = &wasm.functions.values()[symbol.index - wasm.imported_functions_count].func;
28252851 func.type_index = try wasm.putOrGetFuncType(object.func_types[func.type_index]);
28262852 dirty.putAssumeCapacityNoClobber(symbol.index, {});
28272853 }
......@@ -3498,12 +3524,12 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
34983524
34993525 try wasm.markReferences();
35003526 try wasm.setupImports();
3527 try wasm.mergeSections();
3528 try wasm.mergeTypes();
35013529 try wasm.allocateAtoms();
35023530 try wasm.setupMemory();
35033531 wasm.allocateVirtualAddresses();
35043532 wasm.mapFunctionTable();
3505 try wasm.mergeSections();
3506 try wasm.mergeTypes();
35073533 try wasm.initializeCallCtorsFunction();
35083534 try wasm.setupInitMemoryFunction();
35093535 try wasm.setupTLSRelocationsFunction();
......@@ -3639,12 +3665,12 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
36393665 }
36403666 }
36413667
3668 try wasm.mergeSections();
3669 try wasm.mergeTypes();
36423670 try wasm.allocateAtoms();
36433671 try wasm.setupMemory();
36443672 wasm.allocateVirtualAddresses();
36453673 wasm.mapFunctionTable();
3646 try wasm.mergeSections();
3647 try wasm.mergeTypes();
36483674 try wasm.initializeCallCtorsFunction();
36493675 try wasm.setupInitMemoryFunction();
36503676 try wasm.setupTLSRelocationsFunction();
......@@ -3745,7 +3771,7 @@ fn writeToFile(
37453771 if (wasm.functions.count() != 0) {
37463772 const header_offset = try reserveVecSectionHeader(&binary_bytes);
37473773 for (wasm.functions.values()) |function| {
3748 try leb.writeULEB128(binary_writer, function.type_index);
3774 try leb.writeULEB128(binary_writer, function.func.type_index);
37493775 }
37503776
37513777 try writeVecSectionHeader(
......@@ -3916,6 +3942,7 @@ fn writeToFile(
39163942 sorted_atoms.appendAssumeCapacity(atom); // found more code atoms than functions
39173943 atom_index = atom.prev orelse break;
39183944 }
3945 std.debug.assert(wasm.functions.count() == sorted_atoms.items.len);
39193946
39203947 const atom_sort_fn = struct {
39213948 fn sort(ctx: *const Wasm, lhs: *const Atom, rhs: *const Atom) bool {
src/link/Wasm/Symbol.zig+4
......@@ -100,6 +100,10 @@ pub fn mark(symbol: *Symbol) void {
100100 symbol.flags |= @intFromEnum(Flag.alive);
101101}
102102
103pub fn unmark(symbol: *Symbol) void {
104 symbol.flags &= ~@intFromEnum(Flag.alive);
105}
106
103107pub fn isAlive(symbol: Symbol) bool {
104108 return symbol.flags & @intFromEnum(Flag.alive) != 0;
105109}