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) = .{},...@@ -110,7 +110,7 @@ func_types: std.ArrayListUnmanaged(std.wasm.Type) = .{},
110/// Output function section where the key is the original110/// Output function section where the key is the original
111/// function index and the value is function.111/// function index and the value is function.
112/// This allows us to map multiple symbols to the same function.112/// 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 }) = .{},
114/// Output global section114/// Output global section
115wasm_globals: std.ArrayListUnmanaged(std.wasm.Global) = .{},115wasm_globals: std.ArrayListUnmanaged(std.wasm.Global) = .{},
116/// Memory section116/// Memory section
...@@ -1584,7 +1584,7 @@ fn getFunctionSignature(wasm: *const Wasm, loc: SymbolLoc) std.wasm.Type {...@@ -1584,7 +1584,7 @@ fn getFunctionSignature(wasm: *const Wasm, loc: SymbolLoc) std.wasm.Type {
1584 const ty_index = wasm.imports.get(loc).?.kind.function;1584 const ty_index = wasm.imports.get(loc).?.kind.function;
1585 return wasm.func_types.items[ty_index];1585 return wasm.func_types.items[ty_index];
1586 }1586 }
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];
1588}1588}
15891589
1590/// Lowers a constant typed value to a local symbol and atom.1590/// 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 {...@@ -2141,7 +2141,7 @@ fn parseAtom(wasm: *Wasm, atom_index: Atom.Index, kind: Kind) !void {
2141 try wasm.functions.putNoClobber(2141 try wasm.functions.putNoClobber(
2142 wasm.base.allocator,2142 wasm.base.allocator,
2143 .{ .file = null, .index = index },2143 .{ .file = null, .index = index },
2144 .{ .type_index = type_index },2144 .{ .func = .{ .type_index = type_index }, .sym_index = atom.sym_index },
2145 );2145 );
2146 symbol.tag = .function;2146 symbol.tag = .function;
2147 symbol.index = index;2147 symbol.index = index;
...@@ -2274,7 +2274,14 @@ fn allocateAtoms(wasm: *Wasm) !void {...@@ -2274,7 +2274,14 @@ fn allocateAtoms(wasm: *Wasm) !void {
2274 while (true) {2274 while (true) {
2275 const atom = wasm.getAtomPtr(atom_index);2275 const atom = wasm.getAtomPtr(atom_index);
2276 const symbol_loc = atom.symbolLoc();2276 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
2278 if (sym.isDead()) {2285 if (sym.isDead()) {
2279 // Dead symbols must be unlinked from the linked-list to prevent them2286 // Dead symbols must be unlinked from the linked-list to prevent them
2280 // from being emit into the binary.2287 // from being emit into the binary.
...@@ -2477,7 +2484,7 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void {...@@ -2477,7 +2484,7 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void {
2477 // call constructors2484 // call constructors
2478 for (wasm.init_funcs.items) |init_func_loc| {2485 for (wasm.init_funcs.items) |init_func_loc| {
2479 const symbol = init_func_loc.getSymbol(wasm);2486 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;
2481 const ty = wasm.func_types.items[func.type_index];2488 const ty = wasm.func_types.items[func.type_index];
24822489
2483 // Call function by its function index2490 // Call function by its function index
...@@ -2519,7 +2526,7 @@ fn createSyntheticFunction(...@@ -2519,7 +2526,7 @@ fn createSyntheticFunction(
2519 try wasm.functions.putNoClobber(2526 try wasm.functions.putNoClobber(
2520 wasm.base.allocator,2527 wasm.base.allocator,
2521 .{ .file = null, .index = func_index },2528 .{ .file = null, .index = func_index },
2522 .{ .type_index = ty_index },2529 .{ .func = .{ .type_index = ty_index }, .sym_index = loc.index },
2523 );2530 );
2524 symbol.index = func_index;2531 symbol.index = func_index;
25252532
...@@ -2740,6 +2747,9 @@ fn setupImports(wasm: *Wasm) !void {...@@ -2740,6 +2747,9 @@ fn setupImports(wasm: *Wasm) !void {
2740/// Takes the global, function and table section from each linked object file2747/// Takes the global, function and table section from each linked object file
2741/// and merges it into a single section for each.2748/// and merges it into a single section for each.
2742fn mergeSections(wasm: *Wasm) !void {2749fn mergeSections(wasm: *Wasm) !void {
2750 var removed_duplicates = std.ArrayList(SymbolLoc).init(wasm.base.allocator);
2751 defer removed_duplicates.deinit();
2752
2743 for (wasm.resolved_symbols.keys()) |sym_loc| {2753 for (wasm.resolved_symbols.keys()) |sym_loc| {
2744 if (sym_loc.file == null) {2754 if (sym_loc.file == null) {
2745 // Zig code-generated symbols are already within the sections and do not2755 // Zig code-generated symbols are already within the sections and do not
...@@ -2767,9 +2777,19 @@ fn mergeSections(wasm: *Wasm) !void {...@@ -2767,9 +2777,19 @@ fn mergeSections(wasm: *Wasm) !void {
2767 wasm.base.allocator,2777 wasm.base.allocator,
2768 .{ .file = sym_loc.file, .index = symbol.index },2778 .{ .file = sym_loc.file, .index = symbol.index },
2769 );2779 );
2770 if (!gop.found_existing) {2780 if (gop.found_existing) {
2771 gop.value_ptr.* = object.functions[index];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;
2772 }2791 }
2792 gop.value_ptr.* = .{ .func = object.functions[index], .sym_index = sym_loc.index };
2773 symbol.index = @as(u32, @intCast(gop.index)) + wasm.imported_functions_count;2793 symbol.index = @as(u32, @intCast(gop.index)) + wasm.imported_functions_count;
2774 },2794 },
2775 .global => {2795 .global => {
...@@ -2786,6 +2806,12 @@ fn mergeSections(wasm: *Wasm) !void {...@@ -2786,6 +2806,12 @@ fn mergeSections(wasm: *Wasm) !void {
2786 }2806 }
2787 }2807 }
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
2789 log.debug("Merged ({d}) functions", .{wasm.functions.count()});2815 log.debug("Merged ({d}) functions", .{wasm.functions.count()});
2790 log.debug("Merged ({d}) globals", .{wasm.wasm_globals.items.len});2816 log.debug("Merged ({d}) globals", .{wasm.wasm_globals.items.len});
2791 log.debug("Merged ({d}) tables", .{wasm.tables.items.len});2817 log.debug("Merged ({d}) tables", .{wasm.tables.items.len});
...@@ -2821,7 +2847,7 @@ fn mergeTypes(wasm: *Wasm) !void {...@@ -2821,7 +2847,7 @@ fn mergeTypes(wasm: *Wasm) !void {
2821 import.kind.function = try wasm.putOrGetFuncType(original_type);2847 import.kind.function = try wasm.putOrGetFuncType(original_type);
2822 } else if (!dirty.contains(symbol.index)) {2848 } else if (!dirty.contains(symbol.index)) {
2823 log.debug("Adding type from function '{s}'", .{sym_loc.getName(wasm)});2849 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;
2825 func.type_index = try wasm.putOrGetFuncType(object.func_types[func.type_index]);2851 func.type_index = try wasm.putOrGetFuncType(object.func_types[func.type_index]);
2826 dirty.putAssumeCapacityNoClobber(symbol.index, {});2852 dirty.putAssumeCapacityNoClobber(symbol.index, {});
2827 }2853 }
...@@ -3498,12 +3524,12 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l...@@ -3498,12 +3524,12 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
34983524
3499 try wasm.markReferences();3525 try wasm.markReferences();
3500 try wasm.setupImports();3526 try wasm.setupImports();
3527 try wasm.mergeSections();
3528 try wasm.mergeTypes();
3501 try wasm.allocateAtoms();3529 try wasm.allocateAtoms();
3502 try wasm.setupMemory();3530 try wasm.setupMemory();
3503 wasm.allocateVirtualAddresses();3531 wasm.allocateVirtualAddresses();
3504 wasm.mapFunctionTable();3532 wasm.mapFunctionTable();
3505 try wasm.mergeSections();
3506 try wasm.mergeTypes();
3507 try wasm.initializeCallCtorsFunction();3533 try wasm.initializeCallCtorsFunction();
3508 try wasm.setupInitMemoryFunction();3534 try wasm.setupInitMemoryFunction();
3509 try wasm.setupTLSRelocationsFunction();3535 try wasm.setupTLSRelocationsFunction();
...@@ -3639,12 +3665,12 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -3639,12 +3665,12 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
3639 }3665 }
3640 }3666 }
36413667
3668 try wasm.mergeSections();
3669 try wasm.mergeTypes();
3642 try wasm.allocateAtoms();3670 try wasm.allocateAtoms();
3643 try wasm.setupMemory();3671 try wasm.setupMemory();
3644 wasm.allocateVirtualAddresses();3672 wasm.allocateVirtualAddresses();
3645 wasm.mapFunctionTable();3673 wasm.mapFunctionTable();
3646 try wasm.mergeSections();
3647 try wasm.mergeTypes();
3648 try wasm.initializeCallCtorsFunction();3674 try wasm.initializeCallCtorsFunction();
3649 try wasm.setupInitMemoryFunction();3675 try wasm.setupInitMemoryFunction();
3650 try wasm.setupTLSRelocationsFunction();3676 try wasm.setupTLSRelocationsFunction();
...@@ -3745,7 +3771,7 @@ fn writeToFile(...@@ -3745,7 +3771,7 @@ fn writeToFile(
3745 if (wasm.functions.count() != 0) {3771 if (wasm.functions.count() != 0) {
3746 const header_offset = try reserveVecSectionHeader(&binary_bytes);3772 const header_offset = try reserveVecSectionHeader(&binary_bytes);
3747 for (wasm.functions.values()) |function| {3773 for (wasm.functions.values()) |function| {
3748 try leb.writeULEB128(binary_writer, function.type_index);3774 try leb.writeULEB128(binary_writer, function.func.type_index);
3749 }3775 }
37503776
3751 try writeVecSectionHeader(3777 try writeVecSectionHeader(
...@@ -3916,6 +3942,7 @@ fn writeToFile(...@@ -3916,6 +3942,7 @@ fn writeToFile(
3916 sorted_atoms.appendAssumeCapacity(atom); // found more code atoms than functions3942 sorted_atoms.appendAssumeCapacity(atom); // found more code atoms than functions
3917 atom_index = atom.prev orelse break;3943 atom_index = atom.prev orelse break;
3918 }3944 }
3945 std.debug.assert(wasm.functions.count() == sorted_atoms.items.len);
39193946
3920 const atom_sort_fn = struct {3947 const atom_sort_fn = struct {
3921 fn sort(ctx: *const Wasm, lhs: *const Atom, rhs: *const Atom) bool {3948 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 {...@@ -100,6 +100,10 @@ pub fn mark(symbol: *Symbol) void {
100 symbol.flags |= @intFromEnum(Flag.alive);100 symbol.flags |= @intFromEnum(Flag.alive);
101}101}
102102
103pub fn unmark(symbol: *Symbol) void {
104 symbol.flags &= ~@intFromEnum(Flag.alive);
105}
106
103pub fn isAlive(symbol: Symbol) bool {107pub fn isAlive(symbol: Symbol) bool {
104 return symbol.flags & @intFromEnum(Flag.alive) != 0;108 return symbol.flags & @intFromEnum(Flag.alive) != 0;
105}109}