authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-11-29 16:00:23-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-11-29 16:00:23-05:00
logcd7ac56a5a8f79da30c56bed42c30affd9ba0a6d
tree1a436e38a88d25543527209a49e80adb49f4a44e
parent22d7c7d2953360afb29ed2c60185bb0bba32cc30
parent4115f70cd3ac30027618a56976207c3bde378d85
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #18155 from Luukdegram/wasm-gc

wasm-linker: implement garbage-collection and performance improvements

8 files changed, 447 insertions(+), 254 deletions(-)

src/link/Wasm.zig+287-105
...@@ -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
...@@ -1242,6 +1242,14 @@ fn resolveLazySymbols(wasm: *Wasm) !void {...@@ -1242,6 +1242,14 @@ fn resolveLazySymbols(wasm: *Wasm) !void {
1242 if (wasm.undefs.fetchSwapRemove(name_offset)) |kv| {1242 if (wasm.undefs.fetchSwapRemove(name_offset)) |kv| {
1243 const loc = try wasm.createSyntheticSymbolOffset(name_offset, .global);1243 const loc = try wasm.createSyntheticSymbolOffset(name_offset, .global);
1244 try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc);1244 try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc);
1245 _ = wasm.resolved_symbols.swapRemove(kv.value);
1246 const symbol = loc.getSymbol(wasm);
1247 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
1248 symbol.index = @intCast(wasm.imported_globals_count + wasm.wasm_globals.items.len);
1249 try wasm.wasm_globals.append(wasm.base.allocator, .{
1250 .global_type = .{ .valtype = .i32, .mutable = true },
1251 .init = .{ .i32_const = undefined },
1252 });
1245 }1253 }
1246 }1254 }
1247 }1255 }
...@@ -1301,6 +1309,35 @@ pub fn deinit(wasm: *Wasm) void {...@@ -1301,6 +1309,35 @@ pub fn deinit(wasm: *Wasm) void {
1301 archive.deinit(gpa);1309 archive.deinit(gpa);
1302 }1310 }
13031311
1312 // For decls and anon decls we free the memory of its atoms.
1313 // The memory of atoms parsed from object files is managed by
1314 // the object file itself, and therefore we can skip those.
1315 {
1316 var it = wasm.decls.valueIterator();
1317 while (it.next()) |atom_index_ptr| {
1318 const atom = wasm.getAtomPtr(atom_index_ptr.*);
1319 for (atom.locals.items) |local_index| {
1320 const local_atom = wasm.getAtomPtr(local_index);
1321 local_atom.deinit(gpa);
1322 }
1323 atom.deinit(gpa);
1324 }
1325 }
1326 {
1327 for (wasm.anon_decls.values()) |atom_index| {
1328 const atom = wasm.getAtomPtr(atom_index);
1329 for (atom.locals.items) |local_index| {
1330 const local_atom = wasm.getAtomPtr(local_index);
1331 local_atom.deinit(gpa);
1332 }
1333 atom.deinit(gpa);
1334 }
1335 }
1336 for (wasm.synthetic_functions.items) |atom_index| {
1337 const atom = wasm.getAtomPtr(atom_index);
1338 atom.deinit(gpa);
1339 }
1340
1304 wasm.decls.deinit(gpa);1341 wasm.decls.deinit(gpa);
1305 wasm.anon_decls.deinit(gpa);1342 wasm.anon_decls.deinit(gpa);
1306 wasm.atom_types.deinit(gpa);1343 wasm.atom_types.deinit(gpa);
...@@ -1313,9 +1350,6 @@ pub fn deinit(wasm: *Wasm) void {...@@ -1313,9 +1350,6 @@ pub fn deinit(wasm: *Wasm) void {
1313 wasm.symbol_atom.deinit(gpa);1350 wasm.symbol_atom.deinit(gpa);
1314 wasm.export_names.deinit(gpa);1351 wasm.export_names.deinit(gpa);
1315 wasm.atoms.deinit(gpa);1352 wasm.atoms.deinit(gpa);
1316 for (wasm.managed_atoms.items) |*managed_atom| {
1317 managed_atom.deinit(wasm);
1318 }
1319 wasm.managed_atoms.deinit(gpa);1353 wasm.managed_atoms.deinit(gpa);
1320 wasm.segments.deinit(gpa);1354 wasm.segments.deinit(gpa);
1321 wasm.data_segments.deinit(gpa);1355 wasm.data_segments.deinit(gpa);
...@@ -1550,7 +1584,7 @@ fn getFunctionSignature(wasm: *const Wasm, loc: SymbolLoc) std.wasm.Type {...@@ -1550,7 +1584,7 @@ fn getFunctionSignature(wasm: *const Wasm, loc: SymbolLoc) std.wasm.Type {
1550 const ty_index = wasm.imports.get(loc).?.kind.function;1584 const ty_index = wasm.imports.get(loc).?.kind.function;
1551 return wasm.func_types.items[ty_index];1585 return wasm.func_types.items[ty_index];
1552 }1586 }
1553 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];
1554}1588}
15551589
1556/// Lowers a constant typed value to a local symbol and atom.1590/// Lowers a constant typed value to a local symbol and atom.
...@@ -1973,10 +2007,16 @@ pub fn addTableFunction(wasm: *Wasm, symbol_index: u32) !void {...@@ -1973,10 +2007,16 @@ pub fn addTableFunction(wasm: *Wasm, symbol_index: u32) !void {
1973/// Starts at offset 1, where the value `0` represents an unresolved function pointer2007/// Starts at offset 1, where the value `0` represents an unresolved function pointer
1974/// or null-pointer2008/// or null-pointer
1975fn mapFunctionTable(wasm: *Wasm) void {2009fn mapFunctionTable(wasm: *Wasm) void {
1976 var it = wasm.function_table.valueIterator();2010 var it = wasm.function_table.iterator();
1977 var index: u32 = 1;2011 var index: u32 = 1;
1978 while (it.next()) |value_ptr| : (index += 1) {2012 while (it.next()) |entry| {
1979 value_ptr.* = index;2013 const symbol = entry.key_ptr.*.getSymbol(wasm);
2014 if (symbol.isAlive()) {
2015 entry.value_ptr.* = index;
2016 index += 1;
2017 } else {
2018 wasm.function_table.removeByPtr(entry.key_ptr);
2019 }
1980 }2020 }
19812021
1982 if (wasm.base.options.import_table or wasm.base.options.output_mode == .Obj) {2022 if (wasm.base.options.import_table or wasm.base.options.output_mode == .Obj) {
...@@ -2094,20 +2134,28 @@ const Kind = union(enum) {...@@ -2094,20 +2134,28 @@ const Kind = union(enum) {
2094fn parseAtom(wasm: *Wasm, atom_index: Atom.Index, kind: Kind) !void {2134fn parseAtom(wasm: *Wasm, atom_index: Atom.Index, kind: Kind) !void {
2095 const atom = wasm.getAtomPtr(atom_index);2135 const atom = wasm.getAtomPtr(atom_index);
2096 const symbol = (SymbolLoc{ .file = null, .index = atom.sym_index }).getSymbol(wasm);2136 const symbol = (SymbolLoc{ .file = null, .index = atom.sym_index }).getSymbol(wasm);
2137 const do_garbage_collect = wasm.base.options.gc_sections orelse
2138 (wasm.base.options.output_mode != .Obj);
2139
2140 if (symbol.isDead() and do_garbage_collect) {
2141 // Prevent unreferenced symbols from being parsed.
2142 return;
2143 }
2144
2097 const final_index: u32 = switch (kind) {2145 const final_index: u32 = switch (kind) {
2098 .function => result: {2146 .function => result: {
2099 const index = @as(u32, @intCast(wasm.functions.count() + wasm.imported_functions_count));2147 const index: u32 = @intCast(wasm.functions.count() + wasm.imported_functions_count);
2100 const type_index = wasm.atom_types.get(atom_index).?;2148 const type_index = wasm.atom_types.get(atom_index).?;
2101 try wasm.functions.putNoClobber(2149 try wasm.functions.putNoClobber(
2102 wasm.base.allocator,2150 wasm.base.allocator,
2103 .{ .file = null, .index = index },2151 .{ .file = null, .index = index },
2104 .{ .type_index = type_index },2152 .{ .func = .{ .type_index = type_index }, .sym_index = atom.sym_index },
2105 );2153 );
2106 symbol.tag = .function;2154 symbol.tag = .function;
2107 symbol.index = index;2155 symbol.index = index;
21082156
2109 if (wasm.code_section_index == null) {2157 if (wasm.code_section_index == null) {
2110 wasm.code_section_index = @as(u32, @intCast(wasm.segments.items.len));2158 wasm.code_section_index = @intCast(wasm.segments.items.len);
2111 try wasm.segments.append(wasm.base.allocator, .{2159 try wasm.segments.append(wasm.base.allocator, .{
2112 .alignment = atom.alignment,2160 .alignment = atom.alignment,
2113 .size = atom.size,2161 .size = atom.size,
...@@ -2145,12 +2193,12 @@ fn parseAtom(wasm: *Wasm, atom_index: Atom.Index, kind: Kind) !void {...@@ -2145,12 +2193,12 @@ fn parseAtom(wasm: *Wasm, atom_index: Atom.Index, kind: Kind) !void {
2145 const index = gop.value_ptr.*;2193 const index = gop.value_ptr.*;
2146 wasm.segments.items[index].size += atom.size;2194 wasm.segments.items[index].size += atom.size;
21472195
2148 symbol.index = @as(u32, @intCast(wasm.segment_info.getIndex(index).?));2196 symbol.index = @intCast(wasm.segment_info.getIndex(index).?);
2149 // segment info already exists, so free its memory2197 // segment info already exists, so free its memory
2150 wasm.base.allocator.free(segment_name);2198 wasm.base.allocator.free(segment_name);
2151 break :result index;2199 break :result index;
2152 } else {2200 } else {
2153 const index = @as(u32, @intCast(wasm.segments.items.len));2201 const index: u32 = @intCast(wasm.segments.items.len);
2154 var flags: u32 = 0;2202 var flags: u32 = 0;
2155 if (wasm.base.options.shared_memory) {2203 if (wasm.base.options.shared_memory) {
2156 flags |= @intFromEnum(Segment.Flag.WASM_DATA_SEGMENT_IS_PASSIVE);2204 flags |= @intFromEnum(Segment.Flag.WASM_DATA_SEGMENT_IS_PASSIVE);
...@@ -2163,7 +2211,7 @@ fn parseAtom(wasm: *Wasm, atom_index: Atom.Index, kind: Kind) !void {...@@ -2163,7 +2211,7 @@ fn parseAtom(wasm: *Wasm, atom_index: Atom.Index, kind: Kind) !void {
2163 });2211 });
2164 gop.value_ptr.* = index;2212 gop.value_ptr.* = index;
21652213
2166 const info_index = @as(u32, @intCast(wasm.segment_info.count()));2214 const info_index: u32 = @intCast(wasm.segment_info.count());
2167 try wasm.segment_info.put(wasm.base.allocator, index, segment_info);2215 try wasm.segment_info.put(wasm.base.allocator, index, segment_info);
2168 symbol.index = info_index;2216 symbol.index = info_index;
2169 break :result index;2217 break :result index;
...@@ -2234,14 +2282,37 @@ fn allocateAtoms(wasm: *Wasm) !void {...@@ -2234,14 +2282,37 @@ fn allocateAtoms(wasm: *Wasm) !void {
2234 while (true) {2282 while (true) {
2235 const atom = wasm.getAtomPtr(atom_index);2283 const atom = wasm.getAtomPtr(atom_index);
2236 const symbol_loc = atom.symbolLoc();2284 const symbol_loc = atom.symbolLoc();
2237 if (wasm.code_section_index) |index| {2285 // Ensure we get the original symbol, so we verify the correct symbol on whether
2238 if (index == entry.key_ptr.*) {2286 // it is dead or not and ensure an atom is removed when dead.
2239 if (!wasm.resolved_symbols.contains(symbol_loc)) {2287 // This is required as we may have parsed aliases into atoms.
2240 // only allocate resolved function body's.2288 const sym = if (symbol_loc.file) |object_index| sym: {
2241 atom_index = atom.prev orelse break;2289 const object = wasm.objects.items[object_index];
2242 continue;2290 break :sym object.symtable[symbol_loc.index];
2291 } else wasm.symbols.items[symbol_loc.index];
2292
2293 if (sym.isDead()) {
2294 // Dead symbols must be unlinked from the linked-list to prevent them
2295 // from being emit into the binary.
2296 if (atom.next) |next_index| {
2297 const next = wasm.getAtomPtr(next_index);
2298 next.prev = atom.prev;
2299 } else if (entry.value_ptr.* == atom_index) {
2300 // When the atom is dead and is also the first atom retrieved from wasm.atoms(index) we update
2301 // the entry to point it to the previous atom to ensure we do not start with a dead symbol that
2302 // was removed and therefore do not emit any code at all.
2303 if (atom.prev) |prev| {
2304 entry.value_ptr.* = prev;
2243 }2305 }
2244 }2306 }
2307 atom_index = atom.prev orelse {
2308 atom.next = null;
2309 break;
2310 };
2311 const prev = wasm.getAtomPtr(atom_index);
2312 prev.next = atom.next;
2313 atom.prev = null;
2314 atom.next = null;
2315 continue;
2245 }2316 }
2246 offset = @intCast(atom.alignment.forward(offset));2317 offset = @intCast(atom.alignment.forward(offset));
2247 atom.offset = offset;2318 atom.offset = offset;
...@@ -2262,8 +2333,10 @@ fn allocateAtoms(wasm: *Wasm) !void {...@@ -2262,8 +2333,10 @@ fn allocateAtoms(wasm: *Wasm) !void {
2262fn allocateVirtualAddresses(wasm: *Wasm) void {2333fn allocateVirtualAddresses(wasm: *Wasm) void {
2263 for (wasm.resolved_symbols.keys()) |loc| {2334 for (wasm.resolved_symbols.keys()) |loc| {
2264 const symbol = loc.getSymbol(wasm);2335 const symbol = loc.getSymbol(wasm);
2265 if (symbol.tag != .data) {2336 if (symbol.tag != .data or symbol.isDead()) {
2266 continue; // only data symbols have virtual addresses2337 // Only data symbols have virtual addresses.
2338 // Dead symbols do not get allocated, so we don't need to set their virtual address either.
2339 continue;
2267 }2340 }
2268 const atom_index = wasm.symbol_atom.get(loc) orelse {2341 const atom_index = wasm.symbol_atom.get(loc) orelse {
2269 // synthetic symbol that does not contain an atom2342 // synthetic symbol that does not contain an atom
...@@ -2350,11 +2423,17 @@ fn setupInitFunctions(wasm: *Wasm) !void {...@@ -2350,11 +2423,17 @@ fn setupInitFunctions(wasm: *Wasm) !void {
2350 .file = @as(u16, @intCast(file_index)),2423 .file = @as(u16, @intCast(file_index)),
2351 .priority = init_func.priority,2424 .priority = init_func.priority,
2352 });2425 });
2426 try wasm.mark(.{ .index = init_func.symbol_index, .file = @intCast(file_index) });
2353 }2427 }
2354 }2428 }
23552429
2356 // sort the initfunctions based on their priority2430 // sort the initfunctions based on their priority
2357 mem.sort(InitFuncLoc, wasm.init_funcs.items, {}, InitFuncLoc.lessThan);2431 mem.sort(InitFuncLoc, wasm.init_funcs.items, {}, InitFuncLoc.lessThan);
2432
2433 if (wasm.init_funcs.items.len > 0) {
2434 const loc = wasm.findGlobalSymbol("__wasm_call_ctors").?;
2435 try wasm.mark(loc);
2436 }
2358}2437}
23592438
2360/// Generates an atom containing the global error set' size.2439/// Generates an atom containing the global error set' size.
...@@ -2377,7 +2456,7 @@ fn setupErrorsLen(wasm: *Wasm) !void {...@@ -2377,7 +2456,7 @@ fn setupErrorsLen(wasm: *Wasm) !void {
2377 prev_atom.next = atom.next;2456 prev_atom.next = atom.next;
2378 atom.prev = null;2457 atom.prev = null;
2379 }2458 }
2380 atom.deinit(wasm);2459 atom.deinit(wasm.base.allocator);
2381 break :blk index;2460 break :blk index;
2382 } else new_atom: {2461 } else new_atom: {
2383 const atom_index: Atom.Index = @intCast(wasm.managed_atoms.items.len);2462 const atom_index: Atom.Index = @intCast(wasm.managed_atoms.items.len);
...@@ -2422,7 +2501,7 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void {...@@ -2422,7 +2501,7 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void {
2422 // call constructors2501 // call constructors
2423 for (wasm.init_funcs.items) |init_func_loc| {2502 for (wasm.init_funcs.items) |init_func_loc| {
2424 const symbol = init_func_loc.getSymbol(wasm);2503 const symbol = init_func_loc.getSymbol(wasm);
2425 const func = wasm.functions.values()[symbol.index - wasm.imported_functions_count];2504 const func = wasm.functions.values()[symbol.index - wasm.imported_functions_count].func;
2426 const ty = wasm.func_types.items[func.type_index];2505 const ty = wasm.func_types.items[func.type_index];
24272506
2428 // Call function by its function index2507 // Call function by its function index
...@@ -2455,13 +2534,16 @@ fn createSyntheticFunction(...@@ -2455,13 +2534,16 @@ fn createSyntheticFunction(
2455 const loc = wasm.findGlobalSymbol(symbol_name) orelse2534 const loc = wasm.findGlobalSymbol(symbol_name) orelse
2456 try wasm.createSyntheticSymbol(symbol_name, .function);2535 try wasm.createSyntheticSymbol(symbol_name, .function);
2457 const symbol = loc.getSymbol(wasm);2536 const symbol = loc.getSymbol(wasm);
2537 if (symbol.isDead()) {
2538 return;
2539 }
2458 const ty_index = try wasm.putOrGetFuncType(func_ty);2540 const ty_index = try wasm.putOrGetFuncType(func_ty);
2459 // create function with above type2541 // create function with above type
2460 const func_index = wasm.imported_functions_count + @as(u32, @intCast(wasm.functions.count()));2542 const func_index = wasm.imported_functions_count + @as(u32, @intCast(wasm.functions.count()));
2461 try wasm.functions.putNoClobber(2543 try wasm.functions.putNoClobber(
2462 wasm.base.allocator,2544 wasm.base.allocator,
2463 .{ .file = null, .index = func_index },2545 .{ .file = null, .index = func_index },
2464 .{ .type_index = ty_index },2546 .{ .func = .{ .type_index = ty_index }, .sym_index = loc.index },
2465 );2547 );
2466 symbol.index = func_index;2548 symbol.index = func_index;
24672549
...@@ -2477,6 +2559,7 @@ fn createSyntheticFunction(...@@ -2477,6 +2559,7 @@ fn createSyntheticFunction(
2477 .next = null,2559 .next = null,
2478 .prev = null,2560 .prev = null,
2479 .code = function_body.moveToUnmanaged(),2561 .code = function_body.moveToUnmanaged(),
2562 .original_offset = 0,
2480 };2563 };
2481 try wasm.appendAtomAtIndex(wasm.code_section_index.?, atom_index);2564 try wasm.appendAtomAtIndex(wasm.code_section_index.?, atom_index);
2482 try wasm.symbol_atom.putNoClobber(wasm.base.allocator, loc, atom_index);2565 try wasm.symbol_atom.putNoClobber(wasm.base.allocator, loc, atom_index);
...@@ -2513,6 +2596,7 @@ pub fn createFunction(...@@ -2513,6 +2596,7 @@ pub fn createFunction(
2513 .prev = null,2596 .prev = null,
2514 .code = function_body.moveToUnmanaged(),2597 .code = function_body.moveToUnmanaged(),
2515 .relocs = relocations.moveToUnmanaged(),2598 .relocs = relocations.moveToUnmanaged(),
2599 .original_offset = 0,
2516 };2600 };
2517 const symbol = loc.getSymbol(wasm);2601 const symbol = loc.getSymbol(wasm);
2518 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); // ensure function does not get exported2602 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); // ensure function does not get exported
...@@ -2614,21 +2698,21 @@ fn setupImports(wasm: *Wasm) !void {...@@ -2614,21 +2698,21 @@ fn setupImports(wasm: *Wasm) !void {
2614 }2698 }
26152699
2616 for (wasm.resolved_symbols.keys()) |symbol_loc| {2700 for (wasm.resolved_symbols.keys()) |symbol_loc| {
2617 if (symbol_loc.file == null) {2701 const file_index = symbol_loc.file orelse {
2618 // imports generated by Zig code are already in the `import` section2702 // imports generated by Zig code are already in the `import` section
2619 continue;2703 continue;
2620 }2704 };
26212705
2622 const symbol = symbol_loc.getSymbol(wasm);2706 const symbol = symbol_loc.getSymbol(wasm);
2623 if (std.mem.eql(u8, symbol_loc.getName(wasm), "__indirect_function_table")) {2707 if (symbol.isDead() or
2624 continue;2708 !symbol.requiresImport() or
2625 }2709 std.mem.eql(u8, symbol_loc.getName(wasm), "__indirect_function_table"))
2626 if (!symbol.requiresImport()) {2710 {
2627 continue;2711 continue;
2628 }2712 }
26292713
2630 log.debug("Symbol '{s}' will be imported from the host", .{symbol_loc.getName(wasm)});2714 log.debug("Symbol '{s}' will be imported from the host", .{symbol_loc.getName(wasm)});
2631 const object = wasm.objects.items[symbol_loc.file.?];2715 const object = wasm.objects.items[file_index];
2632 const import = object.findImport(symbol.tag.externalType(), symbol.index);2716 const import = object.findImport(symbol.tag.externalType(), symbol.index);
26332717
2634 // We copy the import to a new import to ensure the names contain references2718 // We copy the import to a new import to ensure the names contain references
...@@ -2680,6 +2764,9 @@ fn setupImports(wasm: *Wasm) !void {...@@ -2680,6 +2764,9 @@ fn setupImports(wasm: *Wasm) !void {
2680/// Takes the global, function and table section from each linked object file2764/// Takes the global, function and table section from each linked object file
2681/// and merges it into a single section for each.2765/// and merges it into a single section for each.
2682fn mergeSections(wasm: *Wasm) !void {2766fn mergeSections(wasm: *Wasm) !void {
2767 var removed_duplicates = std.ArrayList(SymbolLoc).init(wasm.base.allocator);
2768 defer removed_duplicates.deinit();
2769
2683 for (wasm.resolved_symbols.keys()) |sym_loc| {2770 for (wasm.resolved_symbols.keys()) |sym_loc| {
2684 if (sym_loc.file == null) {2771 if (sym_loc.file == null) {
2685 // Zig code-generated symbols are already within the sections and do not2772 // Zig code-generated symbols are already within the sections and do not
...@@ -2689,7 +2776,11 @@ fn mergeSections(wasm: *Wasm) !void {...@@ -2689,7 +2776,11 @@ fn mergeSections(wasm: *Wasm) !void {
26892776
2690 const object = &wasm.objects.items[sym_loc.file.?];2777 const object = &wasm.objects.items[sym_loc.file.?];
2691 const symbol = &object.symtable[sym_loc.index];2778 const symbol = &object.symtable[sym_loc.index];
2692 if (symbol.isUndefined() or (symbol.tag != .function and symbol.tag != .global and symbol.tag != .table)) {2779
2780 if (symbol.isDead() or
2781 symbol.isUndefined() or
2782 (symbol.tag != .function and symbol.tag != .global and symbol.tag != .table))
2783 {
2693 // Skip undefined symbols as they go in the `import` section2784 // Skip undefined symbols as they go in the `import` section
2694 // Also skip symbols that do not need to have a section merged.2785 // Also skip symbols that do not need to have a section merged.
2695 continue;2786 continue;
...@@ -2703,9 +2794,20 @@ fn mergeSections(wasm: *Wasm) !void {...@@ -2703,9 +2794,20 @@ fn mergeSections(wasm: *Wasm) !void {
2703 wasm.base.allocator,2794 wasm.base.allocator,
2704 .{ .file = sym_loc.file, .index = symbol.index },2795 .{ .file = sym_loc.file, .index = symbol.index },
2705 );2796 );
2706 if (!gop.found_existing) {2797 if (gop.found_existing) {
2707 gop.value_ptr.* = object.functions[index];2798 // We found an alias to the same function, discard this symbol in favor of
2799 // the original symbol and point the discard function to it. This ensures
2800 // we only emit a single function, instead of duplicates.
2801 symbol.unmark();
2802 try wasm.discarded.putNoClobber(
2803 wasm.base.allocator,
2804 sym_loc,
2805 .{ .file = gop.key_ptr.*.file, .index = gop.value_ptr.*.sym_index },
2806 );
2807 try removed_duplicates.append(sym_loc);
2808 continue;
2708 }2809 }
2810 gop.value_ptr.* = .{ .func = object.functions[index], .sym_index = sym_loc.index };
2709 symbol.index = @as(u32, @intCast(gop.index)) + wasm.imported_functions_count;2811 symbol.index = @as(u32, @intCast(gop.index)) + wasm.imported_functions_count;
2710 },2812 },
2711 .global => {2813 .global => {
...@@ -2722,6 +2824,11 @@ fn mergeSections(wasm: *Wasm) !void {...@@ -2722,6 +2824,11 @@ fn mergeSections(wasm: *Wasm) !void {
2722 }2824 }
2723 }2825 }
27242826
2827 // For any removed duplicates, remove them from the resolved symbols list
2828 for (removed_duplicates.items) |sym_loc| {
2829 assert(wasm.resolved_symbols.swapRemove(sym_loc));
2830 }
2831
2725 log.debug("Merged ({d}) functions", .{wasm.functions.count()});2832 log.debug("Merged ({d}) functions", .{wasm.functions.count()});
2726 log.debug("Merged ({d}) globals", .{wasm.wasm_globals.items.len});2833 log.debug("Merged ({d}) globals", .{wasm.wasm_globals.items.len});
2727 log.debug("Merged ({d}) tables", .{wasm.tables.items.len});2834 log.debug("Merged ({d}) tables", .{wasm.tables.items.len});
...@@ -2745,8 +2852,8 @@ fn mergeTypes(wasm: *Wasm) !void {...@@ -2745,8 +2852,8 @@ fn mergeTypes(wasm: *Wasm) !void {
2745 }2852 }
2746 const object = wasm.objects.items[sym_loc.file.?];2853 const object = wasm.objects.items[sym_loc.file.?];
2747 const symbol = object.symtable[sym_loc.index];2854 const symbol = object.symtable[sym_loc.index];
2748 if (symbol.tag != .function) {2855 if (symbol.tag != .function or symbol.isDead()) {
2749 // Only functions have types2856 // Only functions have types. Only retrieve the type of referenced functions.
2750 continue;2857 continue;
2751 }2858 }
27522859
...@@ -2757,7 +2864,7 @@ fn mergeTypes(wasm: *Wasm) !void {...@@ -2757,7 +2864,7 @@ fn mergeTypes(wasm: *Wasm) !void {
2757 import.kind.function = try wasm.putOrGetFuncType(original_type);2864 import.kind.function = try wasm.putOrGetFuncType(original_type);
2758 } else if (!dirty.contains(symbol.index)) {2865 } else if (!dirty.contains(symbol.index)) {
2759 log.debug("Adding type from function '{s}'", .{sym_loc.getName(wasm)});2866 log.debug("Adding type from function '{s}'", .{sym_loc.getName(wasm)});
2760 const func = &wasm.functions.values()[symbol.index - wasm.imported_functions_count];2867 const func = &wasm.functions.values()[symbol.index - wasm.imported_functions_count].func;
2761 func.type_index = try wasm.putOrGetFuncType(object.func_types[func.type_index]);2868 func.type_index = try wasm.putOrGetFuncType(object.func_types[func.type_index]);
2762 dirty.putAssumeCapacityNoClobber(symbol.index, {});2869 dirty.putAssumeCapacityNoClobber(symbol.index, {});
2763 }2870 }
...@@ -2980,14 +3087,14 @@ fn setupMemory(wasm: *Wasm) !void {...@@ -2980,14 +3087,14 @@ fn setupMemory(wasm: *Wasm) !void {
2980/// From a given object's index and the index of the segment, returns the corresponding3087/// From a given object's index and the index of the segment, returns the corresponding
2981/// index of the segment within the final data section. When the segment does not yet3088/// index of the segment within the final data section. When the segment does not yet
2982/// exist, a new one will be initialized and appended. The new index will be returned in that case.3089/// exist, a new one will be initialized and appended. The new index will be returned in that case.
2983pub fn getMatchingSegment(wasm: *Wasm, object_index: u16, relocatable_index: u32) !?u32 {3090pub fn getMatchingSegment(wasm: *Wasm, object_index: u16, symbol_index: u32) !u32 {
2984 const object: Object = wasm.objects.items[object_index];3091 const object: Object = wasm.objects.items[object_index];
2985 const relocatable_data = object.relocatable_data[relocatable_index];3092 const symbol = object.symtable[symbol_index];
2986 const index = @as(u32, @intCast(wasm.segments.items.len));3093 const index = @as(u32, @intCast(wasm.segments.items.len));
29873094
2988 switch (relocatable_data.type) {3095 switch (symbol.tag) {
2989 .data => {3096 .data => {
2990 const segment_info = object.segment_info[relocatable_data.index];3097 const segment_info = object.segment_info[symbol.index];
2991 const merge_segment = wasm.base.options.output_mode != .Obj;3098 const merge_segment = wasm.base.options.output_mode != .Obj;
2992 const result = try wasm.data_segments.getOrPut(wasm.base.allocator, segment_info.outputName(merge_segment));3099 const result = try wasm.data_segments.getOrPut(wasm.base.allocator, segment_info.outputName(merge_segment));
2993 if (!result.found_existing) {3100 if (!result.found_existing) {
...@@ -3002,70 +3109,75 @@ pub fn getMatchingSegment(wasm: *Wasm, object_index: u16, relocatable_index: u32...@@ -3002,70 +3109,75 @@ pub fn getMatchingSegment(wasm: *Wasm, object_index: u16, relocatable_index: u32
3002 .offset = 0,3109 .offset = 0,
3003 .flags = flags,3110 .flags = flags,
3004 });3111 });
3112 try wasm.segment_info.putNoClobber(wasm.base.allocator, index, .{
3113 .name = try wasm.base.allocator.dupe(u8, segment_info.name),
3114 .alignment = segment_info.alignment,
3115 .flags = segment_info.flags,
3116 });
3005 return index;3117 return index;
3006 } else return result.value_ptr.*;3118 } else return result.value_ptr.*;
3007 },3119 },
3008 .code => return wasm.code_section_index orelse blk: {3120 .function => return wasm.code_section_index orelse blk: {
3009 wasm.code_section_index = index;3121 wasm.code_section_index = index;
3010 try wasm.appendDummySegment();3122 try wasm.appendDummySegment();
3011 break :blk index;3123 break :blk index;
3012 },3124 },
3013 .debug => {3125 .section => {
3014 const debug_name = object.getDebugName(relocatable_data);3126 const section_name = object.string_table.get(symbol.name);
3015 if (mem.eql(u8, debug_name, ".debug_info")) {3127 if (mem.eql(u8, section_name, ".debug_info")) {
3016 return wasm.debug_info_index orelse blk: {3128 return wasm.debug_info_index orelse blk: {
3017 wasm.debug_info_index = index;3129 wasm.debug_info_index = index;
3018 try wasm.appendDummySegment();3130 try wasm.appendDummySegment();
3019 break :blk index;3131 break :blk index;
3020 };3132 };
3021 } else if (mem.eql(u8, debug_name, ".debug_line")) {3133 } else if (mem.eql(u8, section_name, ".debug_line")) {
3022 return wasm.debug_line_index orelse blk: {3134 return wasm.debug_line_index orelse blk: {
3023 wasm.debug_line_index = index;3135 wasm.debug_line_index = index;
3024 try wasm.appendDummySegment();3136 try wasm.appendDummySegment();
3025 break :blk index;3137 break :blk index;
3026 };3138 };
3027 } else if (mem.eql(u8, debug_name, ".debug_loc")) {3139 } else if (mem.eql(u8, section_name, ".debug_loc")) {
3028 return wasm.debug_loc_index orelse blk: {3140 return wasm.debug_loc_index orelse blk: {
3029 wasm.debug_loc_index = index;3141 wasm.debug_loc_index = index;
3030 try wasm.appendDummySegment();3142 try wasm.appendDummySegment();
3031 break :blk index;3143 break :blk index;
3032 };3144 };
3033 } else if (mem.eql(u8, debug_name, ".debug_ranges")) {3145 } else if (mem.eql(u8, section_name, ".debug_ranges")) {
3034 return wasm.debug_line_index orelse blk: {3146 return wasm.debug_line_index orelse blk: {
3035 wasm.debug_ranges_index = index;3147 wasm.debug_ranges_index = index;
3036 try wasm.appendDummySegment();3148 try wasm.appendDummySegment();
3037 break :blk index;3149 break :blk index;
3038 };3150 };
3039 } else if (mem.eql(u8, debug_name, ".debug_pubnames")) {3151 } else if (mem.eql(u8, section_name, ".debug_pubnames")) {
3040 return wasm.debug_pubnames_index orelse blk: {3152 return wasm.debug_pubnames_index orelse blk: {
3041 wasm.debug_pubnames_index = index;3153 wasm.debug_pubnames_index = index;
3042 try wasm.appendDummySegment();3154 try wasm.appendDummySegment();
3043 break :blk index;3155 break :blk index;
3044 };3156 };
3045 } else if (mem.eql(u8, debug_name, ".debug_pubtypes")) {3157 } else if (mem.eql(u8, section_name, ".debug_pubtypes")) {
3046 return wasm.debug_pubtypes_index orelse blk: {3158 return wasm.debug_pubtypes_index orelse blk: {
3047 wasm.debug_pubtypes_index = index;3159 wasm.debug_pubtypes_index = index;
3048 try wasm.appendDummySegment();3160 try wasm.appendDummySegment();
3049 break :blk index;3161 break :blk index;
3050 };3162 };
3051 } else if (mem.eql(u8, debug_name, ".debug_abbrev")) {3163 } else if (mem.eql(u8, section_name, ".debug_abbrev")) {
3052 return wasm.debug_abbrev_index orelse blk: {3164 return wasm.debug_abbrev_index orelse blk: {
3053 wasm.debug_abbrev_index = index;3165 wasm.debug_abbrev_index = index;
3054 try wasm.appendDummySegment();3166 try wasm.appendDummySegment();
3055 break :blk index;3167 break :blk index;
3056 };3168 };
3057 } else if (mem.eql(u8, debug_name, ".debug_str")) {3169 } else if (mem.eql(u8, section_name, ".debug_str")) {
3058 return wasm.debug_str_index orelse blk: {3170 return wasm.debug_str_index orelse blk: {
3059 wasm.debug_str_index = index;3171 wasm.debug_str_index = index;
3060 try wasm.appendDummySegment();3172 try wasm.appendDummySegment();
3061 break :blk index;3173 break :blk index;
3062 };3174 };
3063 } else {3175 } else {
3064 log.warn("found unknown debug section '{s}'", .{debug_name});3176 log.warn("found unknown section '{s}'", .{section_name});
3065 log.warn(" debug section will be skipped", .{});3177 return error.UnexpectedValue;
3066 return null;
3067 }3178 }
3068 },3179 },
3180 else => unreachable,
3069 }3181 }
3070}3182}
30713183
...@@ -3108,6 +3220,7 @@ pub fn getErrorTableSymbol(wasm: *Wasm) !u32 {...@@ -3108,6 +3220,7 @@ pub fn getErrorTableSymbol(wasm: *Wasm) !u32 {
3108 .virtual_address = undefined,3220 .virtual_address = undefined,
3109 };3221 };
3110 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);3222 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
3223 symbol.mark();
31113224
3112 try wasm.resolved_symbols.put(wasm.base.allocator, atom.symbolLoc(), {});3225 try wasm.resolved_symbols.put(wasm.base.allocator, atom.symbolLoc(), {});
31133226
...@@ -3140,6 +3253,7 @@ fn populateErrorNameTable(wasm: *Wasm) !void {...@@ -3140,6 +3253,7 @@ fn populateErrorNameTable(wasm: *Wasm) !void {
3140 .virtual_address = undefined,3253 .virtual_address = undefined,
3141 };3254 };
3142 names_symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);3255 names_symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
3256 names_symbol.mark();
31433257
3144 log.debug("Populating error names", .{});3258 log.debug("Populating error names", .{});
31453259
...@@ -3431,18 +3545,15 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l...@@ -3431,18 +3545,15 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
34313545
3432 try wasm.setupInitFunctions();3546 try wasm.setupInitFunctions();
3433 try wasm.setupStart();3547 try wasm.setupStart();
3434 try wasm.setupImports();
3435
3436 for (wasm.objects.items, 0..) |*object, object_index| {
3437 try object.parseIntoAtoms(gpa, @as(u16, @intCast(object_index)), wasm);
3438 }
34393548
3549 try wasm.markReferences();
3550 try wasm.setupImports();
3551 try wasm.mergeSections();
3552 try wasm.mergeTypes();
3440 try wasm.allocateAtoms();3553 try wasm.allocateAtoms();
3441 try wasm.setupMemory();3554 try wasm.setupMemory();
3442 wasm.allocateVirtualAddresses();3555 wasm.allocateVirtualAddresses();
3443 wasm.mapFunctionTable();3556 wasm.mapFunctionTable();
3444 try wasm.mergeSections();
3445 try wasm.mergeTypes();
3446 try wasm.initializeCallCtorsFunction();3557 try wasm.initializeCallCtorsFunction();
3447 try wasm.setupInitMemoryFunction();3558 try wasm.setupInitMemoryFunction();
3448 try wasm.setupTLSRelocationsFunction();3559 try wasm.setupTLSRelocationsFunction();
...@@ -3519,8 +3630,9 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -3519,8 +3630,9 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
3519 // So we can rebuild the binary file on each incremental update3630 // So we can rebuild the binary file on each incremental update
3520 defer wasm.resetState();3631 defer wasm.resetState();
3521 try wasm.setupInitFunctions();3632 try wasm.setupInitFunctions();
3522 try wasm.setupErrorsLen();
3523 try wasm.setupStart();3633 try wasm.setupStart();
3634 try wasm.markReferences();
3635 try wasm.setupErrorsLen();
3524 try wasm.setupImports();3636 try wasm.setupImports();
3525 if (wasm.base.options.module) |mod| {3637 if (wasm.base.options.module) |mod| {
3526 var decl_it = wasm.decls.iterator();3638 var decl_it = wasm.decls.iterator();
...@@ -3577,16 +3689,12 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -3577,16 +3689,12 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
3577 }3689 }
3578 }3690 }
35793691
3580 for (wasm.objects.items, 0..) |*object, object_index| {3692 try wasm.mergeSections();
3581 try object.parseIntoAtoms(wasm.base.allocator, @as(u16, @intCast(object_index)), wasm);3693 try wasm.mergeTypes();
3582 }
3583
3584 try wasm.allocateAtoms();3694 try wasm.allocateAtoms();
3585 try wasm.setupMemory();3695 try wasm.setupMemory();
3586 wasm.allocateVirtualAddresses();3696 wasm.allocateVirtualAddresses();
3587 wasm.mapFunctionTable();3697 wasm.mapFunctionTable();
3588 try wasm.mergeSections();
3589 try wasm.mergeTypes();
3590 try wasm.initializeCallCtorsFunction();3698 try wasm.initializeCallCtorsFunction();
3591 try wasm.setupInitMemoryFunction();3699 try wasm.setupInitMemoryFunction();
3592 try wasm.setupTLSRelocationsFunction();3700 try wasm.setupTLSRelocationsFunction();
...@@ -3644,8 +3752,8 @@ fn writeToFile(...@@ -3644,8 +3752,8 @@ fn writeToFile(
3644 binary_bytes.items,3752 binary_bytes.items,
3645 header_offset,3753 header_offset,
3646 .type,3754 .type,
3647 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),3755 @intCast(binary_bytes.items.len - header_offset - header_size),
3648 @as(u32, @intCast(wasm.func_types.items.len)),3756 @intCast(wasm.func_types.items.len),
3649 );3757 );
3650 section_count += 1;3758 section_count += 1;
3651 }3759 }
...@@ -3677,8 +3785,8 @@ fn writeToFile(...@@ -3677,8 +3785,8 @@ fn writeToFile(
3677 binary_bytes.items,3785 binary_bytes.items,
3678 header_offset,3786 header_offset,
3679 .import,3787 .import,
3680 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),3788 @intCast(binary_bytes.items.len - header_offset - header_size),
3681 @as(u32, @intCast(wasm.imports.count() + @intFromBool(import_memory))),3789 @intCast(wasm.imports.count() + @intFromBool(import_memory)),
3682 );3790 );
3683 section_count += 1;3791 section_count += 1;
3684 }3792 }
...@@ -3687,15 +3795,15 @@ fn writeToFile(...@@ -3687,15 +3795,15 @@ fn writeToFile(
3687 if (wasm.functions.count() != 0) {3795 if (wasm.functions.count() != 0) {
3688 const header_offset = try reserveVecSectionHeader(&binary_bytes);3796 const header_offset = try reserveVecSectionHeader(&binary_bytes);
3689 for (wasm.functions.values()) |function| {3797 for (wasm.functions.values()) |function| {
3690 try leb.writeULEB128(binary_writer, function.type_index);3798 try leb.writeULEB128(binary_writer, function.func.type_index);
3691 }3799 }
36923800
3693 try writeVecSectionHeader(3801 try writeVecSectionHeader(
3694 binary_bytes.items,3802 binary_bytes.items,
3695 header_offset,3803 header_offset,
3696 .function,3804 .function,
3697 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),3805 @intCast(binary_bytes.items.len - header_offset - header_size),
3698 @as(u32, @intCast(wasm.functions.count())),3806 @intCast(wasm.functions.count()),
3699 );3807 );
3700 section_count += 1;3808 section_count += 1;
3701 }3809 }
...@@ -3713,8 +3821,8 @@ fn writeToFile(...@@ -3713,8 +3821,8 @@ fn writeToFile(
3713 binary_bytes.items,3821 binary_bytes.items,
3714 header_offset,3822 header_offset,
3715 .table,3823 .table,
3716 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),3824 @intCast(binary_bytes.items.len - header_offset - header_size),
3717 @as(u32, @intCast(wasm.tables.items.len)),3825 @intCast(wasm.tables.items.len),
3718 );3826 );
3719 section_count += 1;3827 section_count += 1;
3720 }3828 }
...@@ -3728,8 +3836,8 @@ fn writeToFile(...@@ -3728,8 +3836,8 @@ fn writeToFile(
3728 binary_bytes.items,3836 binary_bytes.items,
3729 header_offset,3837 header_offset,
3730 .memory,3838 .memory,
3731 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),3839 @intCast(binary_bytes.items.len - header_offset - header_size),
3732 @as(u32, 1), // wasm currently only supports 1 linear memory segment3840 1, // wasm currently only supports 1 linear memory segment
3733 );3841 );
3734 section_count += 1;3842 section_count += 1;
3735 }3843 }
...@@ -3748,8 +3856,8 @@ fn writeToFile(...@@ -3748,8 +3856,8 @@ fn writeToFile(
3748 binary_bytes.items,3856 binary_bytes.items,
3749 header_offset,3857 header_offset,
3750 .global,3858 .global,
3751 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),3859 @intCast(binary_bytes.items.len - header_offset - header_size),
3752 @as(u32, @intCast(wasm.wasm_globals.items.len)),3860 @intCast(wasm.wasm_globals.items.len),
3753 );3861 );
3754 section_count += 1;3862 section_count += 1;
3755 }3863 }
...@@ -3777,8 +3885,8 @@ fn writeToFile(...@@ -3777,8 +3885,8 @@ fn writeToFile(
3777 binary_bytes.items,3885 binary_bytes.items,
3778 header_offset,3886 header_offset,
3779 .@"export",3887 .@"export",
3780 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),3888 @intCast(binary_bytes.items.len - header_offset - header_size),
3781 @as(u32, @intCast(wasm.exports.items.len)) + @intFromBool(export_memory),3889 @intCast(wasm.exports.items.len + @intFromBool(export_memory)),
3782 );3890 );
3783 section_count += 1;3891 section_count += 1;
3784 }3892 }
...@@ -3813,15 +3921,16 @@ fn writeToFile(...@@ -3813,15 +3921,16 @@ fn writeToFile(
3813 try leb.writeULEB128(binary_writer, @as(u32, @intCast(wasm.function_table.count())));3921 try leb.writeULEB128(binary_writer, @as(u32, @intCast(wasm.function_table.count())));
3814 var symbol_it = wasm.function_table.keyIterator();3922 var symbol_it = wasm.function_table.keyIterator();
3815 while (symbol_it.next()) |symbol_loc_ptr| {3923 while (symbol_it.next()) |symbol_loc_ptr| {
3816 try leb.writeULEB128(binary_writer, symbol_loc_ptr.*.getSymbol(wasm).index);3924 const sym = symbol_loc_ptr.*.getSymbol(wasm);
3925 try leb.writeULEB128(binary_writer, sym.index);
3817 }3926 }
38183927
3819 try writeVecSectionHeader(3928 try writeVecSectionHeader(
3820 binary_bytes.items,3929 binary_bytes.items,
3821 header_offset,3930 header_offset,
3822 .element,3931 .element,
3823 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),3932 @intCast(binary_bytes.items.len - header_offset - header_size),
3824 @as(u32, 1),3933 1,
3825 );3934 );
3826 section_count += 1;3935 section_count += 1;
3827 }3936 }
...@@ -3834,8 +3943,8 @@ fn writeToFile(...@@ -3834,8 +3943,8 @@ fn writeToFile(
3834 binary_bytes.items,3943 binary_bytes.items,
3835 header_offset,3944 header_offset,
3836 .data_count,3945 .data_count,
3837 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),3946 @intCast(binary_bytes.items.len - header_offset - header_size),
3838 @as(u32, @intCast(data_segments_count)),3947 @intCast(data_segments_count),
3839 );3948 );
3840 }3949 }
38413950
...@@ -3846,20 +3955,18 @@ fn writeToFile(...@@ -3846,20 +3955,18 @@ fn writeToFile(
3846 var atom_index = wasm.atoms.get(code_index).?;3955 var atom_index = wasm.atoms.get(code_index).?;
38473956
3848 // The code section must be sorted in line with the function order.3957 // The code section must be sorted in line with the function order.
3849 var sorted_atoms = try std.ArrayList(*Atom).initCapacity(wasm.base.allocator, wasm.functions.count());3958 var sorted_atoms = try std.ArrayList(*const Atom).initCapacity(wasm.base.allocator, wasm.functions.count());
3850 defer sorted_atoms.deinit();3959 defer sorted_atoms.deinit();
38513960
3852 while (true) {3961 while (true) {
3853 var atom = wasm.getAtomPtr(atom_index);3962 const atom = wasm.getAtomPtr(atom_index);
3854 if (wasm.resolved_symbols.contains(atom.symbolLoc())) {3963 if (!is_obj) {
3855 if (!is_obj) {3964 atom.resolveRelocs(wasm);
3856 atom.resolveRelocs(wasm);
3857 }
3858 sorted_atoms.appendAssumeCapacity(atom);
3859 }3965 }
3860 // atom = if (atom.prev) |prev| wasm.getAtomPtr(prev) else break;3966 sorted_atoms.appendAssumeCapacity(atom); // found more code atoms than functions
3861 atom_index = atom.prev orelse break;3967 atom_index = atom.prev orelse break;
3862 }3968 }
3969 std.debug.assert(wasm.functions.count() == sorted_atoms.items.len);
38633970
3864 const atom_sort_fn = struct {3971 const atom_sort_fn = struct {
3865 fn sort(ctx: *const Wasm, lhs: *const Atom, rhs: *const Atom) bool {3972 fn sort(ctx: *const Wasm, lhs: *const Atom, rhs: *const Atom) bool {
...@@ -3869,7 +3976,7 @@ fn writeToFile(...@@ -3869,7 +3976,7 @@ fn writeToFile(
3869 }3976 }
3870 }.sort;3977 }.sort;
38713978
3872 mem.sort(*Atom, sorted_atoms.items, wasm, atom_sort_fn);3979 mem.sort(*const Atom, sorted_atoms.items, wasm, atom_sort_fn);
38733980
3874 for (sorted_atoms.items) |sorted_atom| {3981 for (sorted_atoms.items) |sorted_atom| {
3875 try leb.writeULEB128(binary_writer, sorted_atom.size);3982 try leb.writeULEB128(binary_writer, sorted_atom.size);
...@@ -3882,7 +3989,7 @@ fn writeToFile(...@@ -3882,7 +3989,7 @@ fn writeToFile(
3882 header_offset,3989 header_offset,
3883 .code,3990 .code,
3884 code_section_size,3991 code_section_size,
3885 @as(u32, @intCast(wasm.functions.count())),3992 @intCast(wasm.functions.count()),
3886 );3993 );
3887 code_section_index = section_count;3994 code_section_index = section_count;
3888 section_count += 1;3995 section_count += 1;
...@@ -3953,8 +4060,8 @@ fn writeToFile(...@@ -3953,8 +4060,8 @@ fn writeToFile(
3953 binary_bytes.items,4060 binary_bytes.items,
3954 header_offset,4061 header_offset,
3955 .data,4062 .data,
3956 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),4063 @intCast(binary_bytes.items.len - header_offset - header_size),
3957 @as(u32, @intCast(segment_count)),4064 @intCast(segment_count),
3958 );4065 );
3959 data_section_index = section_count;4066 data_section_index = section_count;
3960 section_count += 1;4067 section_count += 1;
...@@ -4210,6 +4317,9 @@ fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem...@@ -4210,6 +4317,9 @@ fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem
42104317
4211 for (wasm.resolved_symbols.keys()) |sym_loc| {4318 for (wasm.resolved_symbols.keys()) |sym_loc| {
4212 const symbol = sym_loc.getSymbol(wasm).*;4319 const symbol = sym_loc.getSymbol(wasm).*;
4320 if (symbol.isDead()) {
4321 continue;
4322 }
4213 const name = sym_loc.getName(wasm);4323 const name = sym_loc.getName(wasm);
4214 switch (symbol.tag) {4324 switch (symbol.tag) {
4215 .function => {4325 .function => {
...@@ -4498,6 +4608,14 @@ fn linkWithLLD(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !...@@ -4498,6 +4608,14 @@ fn linkWithLLD(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !
4498 try argv.append("--export-table");4608 try argv.append("--export-table");
4499 }4609 }
45004610
4611 if (wasm.base.options.gc_sections) |gc| {
4612 // For wasm-ld we only need to specify '--no-gc-sections' when the user explicitly
4613 // specified it as garbage collection is enabled by default.
4614 if (!gc) {
4615 try argv.append("--no-gc-sections");
4616 }
4617 }
4618
4501 if (wasm.base.options.strip) {4619 if (wasm.base.options.strip) {
4502 try argv.append("-s");4620 try argv.append("-s");
4503 }4621 }
...@@ -4783,7 +4901,7 @@ fn emitLinkSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), symbol_table:...@@ -4783,7 +4901,7 @@ fn emitLinkSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), symbol_table:
4783 try wasm.emitSymbolTable(binary_bytes, symbol_table);4901 try wasm.emitSymbolTable(binary_bytes, symbol_table);
4784 try wasm.emitSegmentInfo(binary_bytes);4902 try wasm.emitSegmentInfo(binary_bytes);
47854903
4786 const size = @as(u32, @intCast(binary_bytes.items.len - offset - 6));4904 const size: u32 = @intCast(binary_bytes.items.len - offset - 6);
4787 try writeCustomSectionHeader(binary_bytes.items, offset, size);4905 try writeCustomSectionHeader(binary_bytes.items, offset, size);
4788}4906}
47894907
...@@ -4831,7 +4949,7 @@ fn emitSymbolTable(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), symbol_table:...@@ -4831,7 +4949,7 @@ fn emitSymbolTable(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), symbol_table:
4831 }4949 }
48324950
4833 var buf: [10]u8 = undefined;4951 var buf: [10]u8 = undefined;
4834 leb.writeUnsignedFixed(5, buf[0..5], @as(u32, @intCast(binary_bytes.items.len - table_offset + 5)));4952 leb.writeUnsignedFixed(5, buf[0..5], @intCast(binary_bytes.items.len - table_offset + 5));
4835 leb.writeUnsignedFixed(5, buf[5..], symbol_count);4953 leb.writeUnsignedFixed(5, buf[5..], symbol_count);
4836 try binary_bytes.insertSlice(table_offset, &buf);4954 try binary_bytes.insertSlice(table_offset, &buf);
4837}4955}
...@@ -4914,7 +5032,7 @@ fn emitCodeRelocations(...@@ -4914,7 +5032,7 @@ fn emitCodeRelocations(
4914 var buf: [5]u8 = undefined;5032 var buf: [5]u8 = undefined;
4915 leb.writeUnsignedFixed(5, &buf, count);5033 leb.writeUnsignedFixed(5, &buf, count);
4916 try binary_bytes.insertSlice(reloc_start, &buf);5034 try binary_bytes.insertSlice(reloc_start, &buf);
4917 const size = @as(u32, @intCast(binary_bytes.items.len - header_offset - 6));5035 const size: u32 = @intCast(binary_bytes.items.len - header_offset - 6);
4918 try writeCustomSectionHeader(binary_bytes.items, header_offset, size);5036 try writeCustomSectionHeader(binary_bytes.items, header_offset, size);
4919}5037}
49205038
...@@ -5018,3 +5136,67 @@ pub fn storeDeclType(wasm: *Wasm, decl_index: InternPool.DeclIndex, func_type: s...@@ -5018,3 +5136,67 @@ pub fn storeDeclType(wasm: *Wasm, decl_index: InternPool.DeclIndex, func_type: s
5018 try wasm.atom_types.put(wasm.base.allocator, atom_index, index);5136 try wasm.atom_types.put(wasm.base.allocator, atom_index, index);
5019 return index;5137 return index;
5020}5138}
5139
5140/// Verifies all resolved symbols and checks whether itself needs to be marked alive,
5141/// as well as any of its references.
5142fn markReferences(wasm: *Wasm) !void {
5143 const tracy = trace(@src());
5144 defer tracy.end();
5145 const do_garbage_collect = wasm.base.options.gc_sections orelse
5146 (wasm.base.options.output_mode != .Obj);
5147
5148 for (wasm.resolved_symbols.keys()) |sym_loc| {
5149 const sym = sym_loc.getSymbol(wasm);
5150 if (sym.isExported(wasm.base.options.rdynamic) or sym.isNoStrip() or !do_garbage_collect) {
5151 try wasm.mark(sym_loc);
5152 continue;
5153 }
5154
5155 // Debug sections may require to be parsed and marked when it contains
5156 // relocations to alive symbols.
5157 if (sym.tag == .section and !wasm.base.options.strip) {
5158 const file = sym_loc.file orelse continue; // Incremental debug info is done independently
5159 const object = &wasm.objects.items[file];
5160 const atom_index = try Object.parseSymbolIntoAtom(object, file, sym_loc.index, wasm);
5161 const atom = wasm.getAtom(atom_index);
5162 for (atom.relocs.items) |reloc| {
5163 const target_loc: SymbolLoc = .{ .index = reloc.index, .file = atom.file };
5164 const target_sym = target_loc.getSymbol(wasm);
5165 if (target_sym.isAlive() or !do_garbage_collect) {
5166 sym.mark();
5167 continue; // Skip all other relocations as this debug atom is already marked now
5168 }
5169 }
5170 }
5171 }
5172}
5173
5174/// Marks a symbol as 'alive' recursively so itself and any references it contains to
5175/// other symbols will not be omit from the binary.
5176fn mark(wasm: *Wasm, loc: SymbolLoc) !void {
5177 const symbol = loc.getSymbol(wasm);
5178 if (symbol.isAlive()) {
5179 // Symbol is already marked alive, including its references.
5180 // This means we can skip it so we don't end up marking the same symbols
5181 // multiple times.
5182 return;
5183 }
5184 symbol.mark();
5185 if (symbol.isUndefined()) {
5186 // undefined symbols do not have an associated `Atom` and therefore also
5187 // do not contain relocations.
5188 return;
5189 }
5190
5191 const atom_index = if (loc.file) |file_index| idx: {
5192 const object = &wasm.objects.items[file_index];
5193 const atom_index = try object.parseSymbolIntoAtom(file_index, loc.index, wasm);
5194 break :idx atom_index;
5195 } else wasm.symbol_atom.get(loc) orelse return;
5196
5197 const atom = wasm.getAtom(atom_index);
5198 for (atom.relocs.items) |reloc| {
5199 const target_loc: SymbolLoc = .{ .index = reloc.index, .file = loc.file };
5200 try wasm.mark(target_loc.finalLoc(wasm));
5201 }
5202}
src/link/Wasm/Atom.zig+11-7
...@@ -23,6 +23,10 @@ alignment: Wasm.Alignment,...@@ -23,6 +23,10 @@ alignment: Wasm.Alignment,
23/// Offset into the section where the atom lives, this already accounts23/// Offset into the section where the atom lives, this already accounts
24/// for alignment.24/// for alignment.
25offset: u32,25offset: u32,
26/// The original offset within the object file. This value is substracted from
27/// relocation offsets to determine where in the `data` to rewrite the value
28original_offset: u32,
29
26/// Represents the index of the file this atom was generated from.30/// Represents the index of the file this atom was generated from.
27/// This is 'null' when the atom was generated by a Decl from Zig code.31/// This is 'null' when the atom was generated by a Decl from Zig code.
28file: ?u16,32file: ?u16,
...@@ -50,11 +54,11 @@ pub const empty: Atom = .{...@@ -50,11 +54,11 @@ pub const empty: Atom = .{
50 .prev = null,54 .prev = null,
51 .size = 0,55 .size = 0,
52 .sym_index = 0,56 .sym_index = 0,
57 .original_offset = 0,
53};58};
5459
55/// Frees all resources owned by this `Atom`.60/// Frees all resources owned by this `Atom`.
56pub fn deinit(atom: *Atom, wasm: *Wasm) void {61pub fn deinit(atom: *Atom, gpa: std.mem.Allocator) void {
57 const gpa = wasm.base.allocator;
58 atom.relocs.deinit(gpa);62 atom.relocs.deinit(gpa);
59 atom.code.deinit(gpa);63 atom.code.deinit(gpa);
60 atom.locals.deinit(gpa);64 atom.locals.deinit(gpa);
...@@ -114,10 +118,10 @@ pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {...@@ -114,10 +118,10 @@ pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {
114 .R_WASM_GLOBAL_INDEX_I32,118 .R_WASM_GLOBAL_INDEX_I32,
115 .R_WASM_MEMORY_ADDR_I32,119 .R_WASM_MEMORY_ADDR_I32,
116 .R_WASM_SECTION_OFFSET_I32,120 .R_WASM_SECTION_OFFSET_I32,
117 => std.mem.writeInt(u32, atom.code.items[reloc.offset..][0..4], @as(u32, @intCast(value)), .little),121 => std.mem.writeInt(u32, atom.code.items[reloc.offset - atom.original_offset ..][0..4], @as(u32, @intCast(value)), .little),
118 .R_WASM_TABLE_INDEX_I64,122 .R_WASM_TABLE_INDEX_I64,
119 .R_WASM_MEMORY_ADDR_I64,123 .R_WASM_MEMORY_ADDR_I64,
120 => std.mem.writeInt(u64, atom.code.items[reloc.offset..][0..8], value, .little),124 => std.mem.writeInt(u64, atom.code.items[reloc.offset - atom.original_offset ..][0..8], value, .little),
121 .R_WASM_GLOBAL_INDEX_LEB,125 .R_WASM_GLOBAL_INDEX_LEB,
122 .R_WASM_EVENT_INDEX_LEB,126 .R_WASM_EVENT_INDEX_LEB,
123 .R_WASM_FUNCTION_INDEX_LEB,127 .R_WASM_FUNCTION_INDEX_LEB,
...@@ -127,12 +131,12 @@ pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {...@@ -127,12 +131,12 @@ pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {
127 .R_WASM_TABLE_NUMBER_LEB,131 .R_WASM_TABLE_NUMBER_LEB,
128 .R_WASM_TYPE_INDEX_LEB,132 .R_WASM_TYPE_INDEX_LEB,
129 .R_WASM_MEMORY_ADDR_TLS_SLEB,133 .R_WASM_MEMORY_ADDR_TLS_SLEB,
130 => leb.writeUnsignedFixed(5, atom.code.items[reloc.offset..][0..5], @as(u32, @intCast(value))),134 => leb.writeUnsignedFixed(5, atom.code.items[reloc.offset - atom.original_offset ..][0..5], @as(u32, @intCast(value))),
131 .R_WASM_MEMORY_ADDR_LEB64,135 .R_WASM_MEMORY_ADDR_LEB64,
132 .R_WASM_MEMORY_ADDR_SLEB64,136 .R_WASM_MEMORY_ADDR_SLEB64,
133 .R_WASM_TABLE_INDEX_SLEB64,137 .R_WASM_TABLE_INDEX_SLEB64,
134 .R_WASM_MEMORY_ADDR_TLS_SLEB64,138 .R_WASM_MEMORY_ADDR_TLS_SLEB64,
135 => leb.writeUnsignedFixed(10, atom.code.items[reloc.offset..][0..10], value),139 => leb.writeUnsignedFixed(10, atom.code.items[reloc.offset - atom.original_offset ..][0..10], value),
136 }140 }
137 }141 }
138}142}
...@@ -150,7 +154,7 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa...@@ -150,7 +154,7 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa
150 .R_WASM_TABLE_INDEX_I64,154 .R_WASM_TABLE_INDEX_I64,
151 .R_WASM_TABLE_INDEX_SLEB,155 .R_WASM_TABLE_INDEX_SLEB,
152 .R_WASM_TABLE_INDEX_SLEB64,156 .R_WASM_TABLE_INDEX_SLEB64,
153 => return wasm_bin.function_table.get(target_loc) orelse 0,157 => return wasm_bin.function_table.get(.{ .file = atom.file, .index = relocation.index }) orelse 0,
154 .R_WASM_TYPE_INDEX_LEB => {158 .R_WASM_TYPE_INDEX_LEB => {
155 const file_index = atom.file orelse {159 const file_index = atom.file orelse {
156 return relocation.index;160 return relocation.index;
src/link/Wasm/Object.zig+122-142
...@@ -59,20 +59,16 @@ init_funcs: []const types.InitFunc = &.{},...@@ -59,20 +59,16 @@ init_funcs: []const types.InitFunc = &.{},
59comdat_info: []const types.Comdat = &.{},59comdat_info: []const types.Comdat = &.{},
60/// Represents non-synthetic sections that can essentially be mem-cpy'd into place60/// Represents non-synthetic sections that can essentially be mem-cpy'd into place
61/// after performing relocations.61/// after performing relocations.
62relocatable_data: []const RelocatableData = &.{},62relocatable_data: std.AutoHashMapUnmanaged(RelocatableData.Tag, []RelocatableData) = .{},
63/// String table for all strings required by the object file, such as symbol names,63/// String table for all strings required by the object file, such as symbol names,
64/// import name, module name and export names. Each string will be deduplicated64/// import name, module name and export names. Each string will be deduplicated
65/// and returns an offset into the table.65/// and returns an offset into the table.
66string_table: Wasm.StringTable = .{},66string_table: Wasm.StringTable = .{},
67/// All the names of each debug section found in the current object file.
68/// Each name is terminated by a null-terminator. The name can be found,
69/// from the `index` offset within the `RelocatableData`.
70debug_names: [:0]const u8,
7167
72/// Represents a single item within a section (depending on its `type`)68/// Represents a single item within a section (depending on its `type`)
73const RelocatableData = struct {69const RelocatableData = struct {
74 /// The type of the relocatable data70 /// The type of the relocatable data
75 type: enum { data, code, debug },71 type: Tag,
76 /// Pointer to the data of the segment, where its length is written to `size`72 /// Pointer to the data of the segment, where its length is written to `size`
77 data: [*]u8,73 data: [*]u8,
78 /// The size in bytes of the data representing the segment within the section74 /// The size in bytes of the data representing the segment within the section
...@@ -85,6 +81,8 @@ const RelocatableData = struct {...@@ -85,6 +81,8 @@ const RelocatableData = struct {
85 /// Represents the index of the section it belongs to81 /// Represents the index of the section it belongs to
86 section_index: u32,82 section_index: u32,
8783
84 const Tag = enum { data, code, custom };
85
88 /// Returns the alignment of the segment, by retrieving it from the segment86 /// Returns the alignment of the segment, by retrieving it from the segment
89 /// meta data of the given object file.87 /// meta data of the given object file.
90 /// NOTE: Alignment is encoded as a power of 2, so we shift the symbol's88 /// NOTE: Alignment is encoded as a power of 2, so we shift the symbol's
...@@ -99,14 +97,14 @@ const RelocatableData = struct {...@@ -99,14 +97,14 @@ const RelocatableData = struct {
99 return switch (relocatable_data.type) {97 return switch (relocatable_data.type) {
100 .data => .data,98 .data => .data,
101 .code => .function,99 .code => .function,
102 .debug => .section,100 .custom => .section,
103 };101 };
104 }102 }
105103
106 /// Returns the index within a section itrelocatable_data, or in case of a debug section,104 /// Returns the index within a section, or in case of a custom section,
107 /// returns the section index within the object file.105 /// returns the section index within the object file.
108 pub fn getIndex(relocatable_data: RelocatableData) u32 {106 pub fn getIndex(relocatable_data: RelocatableData) u32 {
109 if (relocatable_data.type == .debug) return relocatable_data.section_index;107 if (relocatable_data.type == .custom) return relocatable_data.section_index;
110 return relocatable_data.index;108 return relocatable_data.index;
111 }109 }
112};110};
...@@ -121,7 +119,6 @@ pub fn create(gpa: Allocator, file: std.fs.File, name: []const u8, maybe_max_siz...@@ -121,7 +119,6 @@ pub fn create(gpa: Allocator, file: std.fs.File, name: []const u8, maybe_max_siz
121 var object: Object = .{119 var object: Object = .{
122 .file = file,120 .file = file,
123 .name = try gpa.dupe(u8, name),121 .name = try gpa.dupe(u8, name),
124 .debug_names = &.{},
125 };122 };
126123
127 var is_object_file: bool = false;124 var is_object_file: bool = false;
...@@ -182,10 +179,16 @@ pub fn deinit(object: *Object, gpa: Allocator) void {...@@ -182,10 +179,16 @@ pub fn deinit(object: *Object, gpa: Allocator) void {
182 gpa.free(info.name);179 gpa.free(info.name);
183 }180 }
184 gpa.free(object.segment_info);181 gpa.free(object.segment_info);
185 for (object.relocatable_data) |rel_data| {182 {
186 gpa.free(rel_data.data[0..rel_data.size]);183 var it = object.relocatable_data.valueIterator();
184 while (it.next()) |relocatable_data| {
185 for (relocatable_data.*) |rel_data| {
186 gpa.free(rel_data.data[0..rel_data.size]);
187 }
188 gpa.free(relocatable_data.*);
189 }
187 }190 }
188 gpa.free(object.relocatable_data);191 object.relocatable_data.deinit(gpa);
189 object.string_table.deinit(gpa);192 object.string_table.deinit(gpa);
190 gpa.free(object.name);193 gpa.free(object.name);
191 object.* = undefined;194 object.* = undefined;
...@@ -345,23 +348,7 @@ fn Parser(comptime ReaderType: type) type {...@@ -345,23 +348,7 @@ fn Parser(comptime ReaderType: type) type {
345 errdefer parser.object.deinit(gpa);348 errdefer parser.object.deinit(gpa);
346 try parser.verifyMagicBytes();349 try parser.verifyMagicBytes();
347 const version = try parser.reader.reader().readInt(u32, .little);350 const version = try parser.reader.reader().readInt(u32, .little);
348
349 parser.object.version = version;351 parser.object.version = version;
350 var relocatable_data = std.ArrayList(RelocatableData).init(gpa);
351 var debug_names = std.ArrayList(u8).init(gpa);
352
353 errdefer {
354 // only free the inner contents of relocatable_data if we didn't
355 // assign it to the object yet.
356 if (parser.object.relocatable_data.len == 0) {
357 for (relocatable_data.items) |rel_data| {
358 gpa.free(rel_data.data[0..rel_data.size]);
359 }
360 relocatable_data.deinit();
361 }
362 gpa.free(debug_names.items);
363 debug_names.deinit();
364 }
365352
366 var section_index: u32 = 0;353 var section_index: u32 = 0;
367 while (parser.reader.reader().readByte()) |byte| : (section_index += 1) {354 while (parser.reader.reader().readByte()) |byte| : (section_index += 1) {
...@@ -377,26 +364,34 @@ fn Parser(comptime ReaderType: type) type {...@@ -377,26 +364,34 @@ fn Parser(comptime ReaderType: type) type {
377364
378 if (std.mem.eql(u8, name, "linking")) {365 if (std.mem.eql(u8, name, "linking")) {
379 is_object_file.* = true;366 is_object_file.* = true;
380 parser.object.relocatable_data = relocatable_data.items; // at this point no new relocatable sections will appear so we're free to store them.
381 try parser.parseMetadata(gpa, @as(usize, @intCast(reader.context.bytes_left)));367 try parser.parseMetadata(gpa, @as(usize, @intCast(reader.context.bytes_left)));
382 } else if (std.mem.startsWith(u8, name, "reloc")) {368 } else if (std.mem.startsWith(u8, name, "reloc")) {
383 try parser.parseRelocations(gpa);369 try parser.parseRelocations(gpa);
384 } else if (std.mem.eql(u8, name, "target_features")) {370 } else if (std.mem.eql(u8, name, "target_features")) {
385 try parser.parseFeatures(gpa);371 try parser.parseFeatures(gpa);
386 } else if (std.mem.startsWith(u8, name, ".debug")) {372 } else if (std.mem.startsWith(u8, name, ".debug")) {
373 const gop = try parser.object.relocatable_data.getOrPut(gpa, .custom);
374 var relocatable_data: std.ArrayListUnmanaged(RelocatableData) = .{};
375 defer relocatable_data.deinit(gpa);
376 if (!gop.found_existing) {
377 gop.value_ptr.* = &.{};
378 } else {
379 relocatable_data = std.ArrayListUnmanaged(RelocatableData).fromOwnedSlice(gop.value_ptr.*);
380 }
387 const debug_size = @as(u32, @intCast(reader.context.bytes_left));381 const debug_size = @as(u32, @intCast(reader.context.bytes_left));
388 const debug_content = try gpa.alloc(u8, debug_size);382 const debug_content = try gpa.alloc(u8, debug_size);
389 errdefer gpa.free(debug_content);383 errdefer gpa.free(debug_content);
390 try reader.readNoEof(debug_content);384 try reader.readNoEof(debug_content);
391385
392 try relocatable_data.append(.{386 try relocatable_data.append(gpa, .{
393 .type = .debug,387 .type = .custom,
394 .data = debug_content.ptr,388 .data = debug_content.ptr,
395 .size = debug_size,389 .size = debug_size,
396 .index = try parser.object.string_table.put(gpa, name),390 .index = try parser.object.string_table.put(gpa, name),
397 .offset = 0, // debug sections only contain 1 entry, so no need to calculate offset391 .offset = 0, // debug sections only contain 1 entry, so no need to calculate offset
398 .section_index = section_index,392 .section_index = section_index,
399 });393 });
394 gop.value_ptr.* = try relocatable_data.toOwnedSlice(gpa);
400 } else {395 } else {
401 try reader.skipBytes(reader.context.bytes_left, .{});396 try reader.skipBytes(reader.context.bytes_left, .{});
402 }397 }
...@@ -515,26 +510,32 @@ fn Parser(comptime ReaderType: type) type {...@@ -515,26 +510,32 @@ fn Parser(comptime ReaderType: type) type {
515 const start = reader.context.bytes_left;510 const start = reader.context.bytes_left;
516 var index: u32 = 0;511 var index: u32 = 0;
517 const count = try readLeb(u32, reader);512 const count = try readLeb(u32, reader);
513 const imported_function_count = parser.object.importedCountByKind(.function);
514 var relocatable_data = try std.ArrayList(RelocatableData).initCapacity(gpa, count);
515 defer relocatable_data.deinit();
518 while (index < count) : (index += 1) {516 while (index < count) : (index += 1) {
519 const code_len = try readLeb(u32, reader);517 const code_len = try readLeb(u32, reader);
520 const offset = @as(u32, @intCast(start - reader.context.bytes_left));518 const offset = @as(u32, @intCast(start - reader.context.bytes_left));
521 const data = try gpa.alloc(u8, code_len);519 const data = try gpa.alloc(u8, code_len);
522 errdefer gpa.free(data);520 errdefer gpa.free(data);
523 try reader.readNoEof(data);521 try reader.readNoEof(data);
524 try relocatable_data.append(.{522 relocatable_data.appendAssumeCapacity(.{
525 .type = .code,523 .type = .code,
526 .data = data.ptr,524 .data = data.ptr,
527 .size = code_len,525 .size = code_len,
528 .index = parser.object.importedCountByKind(.function) + index,526 .index = imported_function_count + index,
529 .offset = offset,527 .offset = offset,
530 .section_index = section_index,528 .section_index = section_index,
531 });529 });
532 }530 }
531 try parser.object.relocatable_data.put(gpa, .code, try relocatable_data.toOwnedSlice());
533 },532 },
534 .data => {533 .data => {
535 const start = reader.context.bytes_left;534 const start = reader.context.bytes_left;
536 var index: u32 = 0;535 var index: u32 = 0;
537 const count = try readLeb(u32, reader);536 const count = try readLeb(u32, reader);
537 var relocatable_data = try std.ArrayList(RelocatableData).initCapacity(gpa, count);
538 defer relocatable_data.deinit();
538 while (index < count) : (index += 1) {539 while (index < count) : (index += 1) {
539 const flags = try readLeb(u32, reader);540 const flags = try readLeb(u32, reader);
540 const data_offset = try readInit(reader);541 const data_offset = try readInit(reader);
...@@ -545,7 +546,7 @@ fn Parser(comptime ReaderType: type) type {...@@ -545,7 +546,7 @@ fn Parser(comptime ReaderType: type) type {
545 const data = try gpa.alloc(u8, data_len);546 const data = try gpa.alloc(u8, data_len);
546 errdefer gpa.free(data);547 errdefer gpa.free(data);
547 try reader.readNoEof(data);548 try reader.readNoEof(data);
548 try relocatable_data.append(.{549 relocatable_data.appendAssumeCapacity(.{
549 .type = .data,550 .type = .data,
550 .data = data.ptr,551 .data = data.ptr,
551 .size = data_len,552 .size = data_len,
...@@ -554,6 +555,7 @@ fn Parser(comptime ReaderType: type) type {...@@ -554,6 +555,7 @@ fn Parser(comptime ReaderType: type) type {
554 .section_index = section_index,555 .section_index = section_index,
555 });556 });
556 }557 }
558 try parser.object.relocatable_data.put(gpa, .data, try relocatable_data.toOwnedSlice());
557 },559 },
558 else => try parser.reader.reader().skipBytes(len, .{}),560 else => try parser.reader.reader().skipBytes(len, .{}),
559 }561 }
...@@ -561,7 +563,6 @@ fn Parser(comptime ReaderType: type) type {...@@ -561,7 +563,6 @@ fn Parser(comptime ReaderType: type) type {
561 error.EndOfStream => {}, // finished parsing the file563 error.EndOfStream => {}, // finished parsing the file
562 else => |e| return e,564 else => |e| return e,
563 }565 }
564 parser.object.relocatable_data = try relocatable_data.toOwnedSlice();
565 }566 }
566567
567 /// Based on the "features" custom section, parses it into a list of568 /// Based on the "features" custom section, parses it into a list of
...@@ -789,7 +790,8 @@ fn Parser(comptime ReaderType: type) type {...@@ -789,7 +790,8 @@ fn Parser(comptime ReaderType: type) type {
789 },790 },
790 .section => {791 .section => {
791 symbol.index = try leb.readULEB128(u32, reader);792 symbol.index = try leb.readULEB128(u32, reader);
792 for (parser.object.relocatable_data) |data| {793 const section_data = parser.object.relocatable_data.get(.custom).?;
794 for (section_data) |data| {
793 if (data.section_index == symbol.index) {795 if (data.section_index == symbol.index) {
794 symbol.name = data.index;796 symbol.name = data.index;
795 break;797 break;
...@@ -798,22 +800,15 @@ fn Parser(comptime ReaderType: type) type {...@@ -798,22 +800,15 @@ fn Parser(comptime ReaderType: type) type {
798 },800 },
799 else => {801 else => {
800 symbol.index = try leb.readULEB128(u32, reader);802 symbol.index = try leb.readULEB128(u32, reader);
801 var maybe_import: ?types.Import = null;
802
803 const is_undefined = symbol.isUndefined();803 const is_undefined = symbol.isUndefined();
804 if (is_undefined) {
805 maybe_import = parser.object.findImport(symbol.tag.externalType(), symbol.index);
806 }
807 const explicit_name = symbol.hasFlag(.WASM_SYM_EXPLICIT_NAME);804 const explicit_name = symbol.hasFlag(.WASM_SYM_EXPLICIT_NAME);
808 if (!(is_undefined and !explicit_name)) {805 symbol.name = if (!is_undefined or (is_undefined and explicit_name)) name: {
809 const name_len = try leb.readULEB128(u32, reader);806 const name_len = try leb.readULEB128(u32, reader);
810 const name = try gpa.alloc(u8, name_len);807 const name = try gpa.alloc(u8, name_len);
811 defer gpa.free(name);808 defer gpa.free(name);
812 try reader.readNoEof(name);809 try reader.readNoEof(name);
813 symbol.name = try parser.object.string_table.put(gpa, name);810 break :name try parser.object.string_table.put(gpa, name);
814 } else {811 } else parser.object.findImport(symbol.tag.externalType(), symbol.index).name;
815 symbol.name = maybe_import.?.name;
816 }
817 },812 },
818 }813 }
819 return symbol;814 return symbol;
...@@ -887,110 +882,95 @@ fn assertEnd(reader: anytype) !void {...@@ -887,110 +882,95 @@ fn assertEnd(reader: anytype) !void {
887}882}
888883
889/// Parses an object file into atoms, for code and data sections884/// Parses an object file into atoms, for code and data sections
890pub fn parseIntoAtoms(object: *Object, gpa: Allocator, object_index: u16, wasm_bin: *Wasm) !void {885pub fn parseSymbolIntoAtom(object: *Object, object_index: u16, symbol_index: u32, wasm: *Wasm) !Atom.Index {
891 const Key = struct {886 const symbol = &object.symtable[symbol_index];
892 kind: Symbol.Tag,887 const relocatable_data: RelocatableData = switch (symbol.tag) {
893 index: u32,888 .function => object.relocatable_data.get(.code).?[symbol.index - object.importedCountByKind(.function)],
894 };889 .data => object.relocatable_data.get(.data).?[symbol.index],
895 var symbol_for_segment = std.AutoArrayHashMap(Key, std.ArrayList(u32)).init(gpa);890 .section => blk: {
896 defer for (symbol_for_segment.values()) |*list| {891 const data = object.relocatable_data.get(.custom).?;
897 list.deinit();892 for (data) |dat| {
898 } else symbol_for_segment.deinit();893 if (dat.section_index == symbol.index) {
899894 break :blk dat;
900 for (object.symtable, 0..) |symbol, symbol_index| {
901 switch (symbol.tag) {
902 .function, .data, .section => if (!symbol.isUndefined()) {
903 const gop = try symbol_for_segment.getOrPut(.{ .kind = symbol.tag, .index = symbol.index });
904 const sym_idx = @as(u32, @intCast(symbol_index));
905 if (!gop.found_existing) {
906 gop.value_ptr.* = std.ArrayList(u32).init(gpa);
907 }895 }
908 try gop.value_ptr.*.append(sym_idx);896 }
909 },897 unreachable;
910 else => continue,898 },
911 }899 else => unreachable,
900 };
901 const final_index = try wasm.getMatchingSegment(object_index, symbol_index);
902 const atom_index = @as(Atom.Index, @intCast(wasm.managed_atoms.items.len));
903 const atom = try wasm.managed_atoms.addOne(wasm.base.allocator);
904 atom.* = Atom.empty;
905 try wasm.appendAtomAtIndex(final_index, atom_index);
906
907 atom.sym_index = symbol_index;
908 atom.file = object_index;
909 atom.size = relocatable_data.size;
910 atom.alignment = relocatable_data.getAlignment(object);
911 atom.code = std.ArrayListUnmanaged(u8).fromOwnedSlice(relocatable_data.data[0..relocatable_data.size]);
912 atom.original_offset = relocatable_data.offset;
913 try wasm.symbol_atom.putNoClobber(wasm.base.allocator, atom.symbolLoc(), atom_index);
914 const segment: *Wasm.Segment = &wasm.segments.items[final_index];
915 if (relocatable_data.type == .data) { //code section and custom sections are 1-byte aligned
916 segment.alignment = segment.alignment.max(atom.alignment);
912 }917 }
913918
914 for (object.relocatable_data, 0..) |relocatable_data, index| {919 if (object.relocations.get(relocatable_data.section_index)) |relocations| {
915 const final_index = (try wasm_bin.getMatchingSegment(object_index, @as(u32, @intCast(index)))) orelse {920 const start = searchRelocStart(relocations, relocatable_data.offset);
916 continue; // found unknown section, so skip parsing into atom as we do not know how to handle it.921 const len = searchRelocEnd(relocations[start..], relocatable_data.offset + atom.size);
917 };922 atom.relocs = std.ArrayListUnmanaged(types.Relocation).fromOwnedSlice(relocations[start..][0..len]);
918923 for (atom.relocs.items) |reloc| {
919 const atom_index: Atom.Index = @intCast(wasm_bin.managed_atoms.items.len);924 switch (reloc.relocation_type) {
920 const atom = try wasm_bin.managed_atoms.addOne(gpa);925 .R_WASM_TABLE_INDEX_I32,
921 atom.* = Atom.empty;926 .R_WASM_TABLE_INDEX_I64,
922 atom.file = object_index;927 .R_WASM_TABLE_INDEX_SLEB,
923 atom.size = relocatable_data.size;928 .R_WASM_TABLE_INDEX_SLEB64,
924 atom.alignment = relocatable_data.getAlignment(object);929 => {
925930 try wasm.function_table.put(wasm.base.allocator, .{
926 const relocations: []types.Relocation = object.relocations.get(relocatable_data.section_index) orelse &.{};931 .file = object_index,
927 for (relocations) |relocation| {932 .index = reloc.index,
928 if (isInbetween(relocatable_data.offset, atom.size, relocation.offset)) {933 }, 0);
929 // set the offset relative to the offset of the segment itobject,934 },
930 // rather than within the entire section.935 .R_WASM_GLOBAL_INDEX_I32,
931 var reloc = relocation;936 .R_WASM_GLOBAL_INDEX_LEB,
932 reloc.offset -= relocatable_data.offset;937 => {
933 try atom.relocs.append(gpa, reloc);938 const sym = object.symtable[reloc.index];
934939 if (sym.tag != .global) {
935 switch (relocation.relocation_type) {940 try wasm.got_symbols.append(
936 .R_WASM_TABLE_INDEX_I32,941 wasm.base.allocator,
937 .R_WASM_TABLE_INDEX_I64,942 .{ .file = object_index, .index = reloc.index },
938 .R_WASM_TABLE_INDEX_SLEB,943 );
939 .R_WASM_TABLE_INDEX_SLEB64,944 }
940 => {945 },
941 try wasm_bin.function_table.put(gpa, .{946 else => {},
942 .file = object_index,
943 .index = relocation.index,
944 }, 0);
945 },
946 .R_WASM_GLOBAL_INDEX_I32,
947 .R_WASM_GLOBAL_INDEX_LEB,
948 => {
949 const sym = object.symtable[relocation.index];
950 if (sym.tag != .global) {
951 try wasm_bin.got_symbols.append(
952 wasm_bin.base.allocator,
953 .{ .file = object_index, .index = relocation.index },
954 );
955 }
956 },
957 else => {},
958 }
959 }947 }
960 }948 }
949 }
961950
962 try atom.code.appendSlice(gpa, relocatable_data.data[0..relocatable_data.size]);951 return atom_index;
963952}
964 if (symbol_for_segment.getPtr(.{
965 .kind = relocatable_data.getSymbolKind(),
966 .index = relocatable_data.getIndex(),
967 })) |symbols| {
968 atom.sym_index = symbols.pop();
969 try wasm_bin.symbol_atom.putNoClobber(gpa, atom.symbolLoc(), atom_index);
970
971 // symbols referencing the same atom will be added as alias
972 // or as 'parent' when they are global.
973 while (symbols.popOrNull()) |idx| {
974 try wasm_bin.symbol_atom.putNoClobber(gpa, .{ .file = atom.file, .index = idx }, atom_index);
975 const alias_symbol = object.symtable[idx];
976 if (alias_symbol.isGlobal()) {
977 atom.sym_index = idx;
978 }
979 }
980 }
981953
982 const segment: *Wasm.Segment = &wasm_bin.segments.items[final_index];954fn searchRelocStart(relocs: []const types.Relocation, address: u32) usize {
983 if (relocatable_data.type == .data) { //code section and debug sections are 1-byte aligned955 var min: usize = 0;
984 segment.alignment = segment.alignment.max(atom.alignment);956 var max: usize = relocs.len;
957 while (min < max) {
958 const index = (min + max) / 2;
959 const curr = relocs[index];
960 if (curr.offset < address) {
961 min = index + 1;
962 } else {
963 max = index;
985 }964 }
986
987 try wasm_bin.appendAtomAtIndex(final_index, atom_index);
988 log.debug("Parsed into atom: '{s}' at segment index {d}", .{ object.string_table.get(object.symtable[atom.sym_index].name), final_index });
989 }965 }
966 return min;
990}967}
991968
992/// Verifies if a given value is in between a minimum -and maximum value.969fn searchRelocEnd(relocs: []const types.Relocation, address: u32) usize {
993/// The maxmimum value is calculated using the length, both start and end are inclusive.970 for (relocs, 0..relocs.len) |reloc, index| {
994inline fn isInbetween(min: u32, length: u32, value: u32) bool {971 if (reloc.offset > address) {
995 return value >= min and value <= min + length;972 return index;
973 }
974 }
975 return relocs.len;
996}976}
src/link/Wasm/Symbol.zig+20
...@@ -79,6 +79,9 @@ pub const Flag = enum(u32) {...@@ -79,6 +79,9 @@ pub const Flag = enum(u32) {
79 WASM_SYM_NO_STRIP = 0x80,79 WASM_SYM_NO_STRIP = 0x80,
80 /// Indicates a symbol is TLS80 /// Indicates a symbol is TLS
81 WASM_SYM_TLS = 0x100,81 WASM_SYM_TLS = 0x100,
82 /// Zig specific flag. Uses the most significant bit of the flag to annotate whether a symbol is
83 /// alive or not. Dead symbols are allowed to be garbage collected.
84 alive = 0x80000000,
82};85};
8386
84/// Verifies if the given symbol should be imported from the87/// Verifies if the given symbol should be imported from the
...@@ -92,6 +95,23 @@ pub fn requiresImport(symbol: Symbol) bool {...@@ -92,6 +95,23 @@ pub fn requiresImport(symbol: Symbol) bool {
92 return true;95 return true;
93}96}
9497
98/// Marks a symbol as 'alive', ensuring the garbage collector will not collect the trash.
99pub fn mark(symbol: *Symbol) void {
100 symbol.flags |= @intFromEnum(Flag.alive);
101}
102
103pub fn unmark(symbol: *Symbol) void {
104 symbol.flags &= ~@intFromEnum(Flag.alive);
105}
106
107pub fn isAlive(symbol: Symbol) bool {
108 return symbol.flags & @intFromEnum(Flag.alive) != 0;
109}
110
111pub fn isDead(symbol: Symbol) bool {
112 return symbol.flags & @intFromEnum(Flag.alive) == 0;
113}
114
95pub fn isTLS(symbol: Symbol) bool {115pub fn isTLS(symbol: Symbol) bool {
96 return symbol.flags & @intFromEnum(Flag.WASM_SYM_TLS) != 0;116 return symbol.flags & @intFromEnum(Flag.WASM_SYM_TLS) != 0;
97}117}
test/link/wasm/bss/build.zig+2
...@@ -26,6 +26,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize_mode: std.builtin.Opt...@@ -26,6 +26,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize_mode: std.builtin.Opt
26 lib.strip = false;26 lib.strip = false;
27 // to make sure the bss segment is emitted, we must import memory27 // to make sure the bss segment is emitted, we must import memory
28 lib.import_memory = true;28 lib.import_memory = true;
29 lib.link_gc_sections = false;
2930
30 const check_lib = lib.checkObject();31 const check_lib = lib.checkObject();
3132
...@@ -73,6 +74,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize_mode: std.builtin.Opt...@@ -73,6 +74,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize_mode: std.builtin.Opt
73 lib.strip = false;74 lib.strip = false;
74 // to make sure the bss segment is emitted, we must import memory75 // to make sure the bss segment is emitted, we must import memory
75 lib.import_memory = true;76 lib.import_memory = true;
77 lib.link_gc_sections = false;
7678
77 const check_lib = lib.checkObject();79 const check_lib = lib.checkObject();
78 check_lib.checkStart();80 check_lib.checkStart();
test/link/wasm/function-table/build.zig+3
...@@ -23,6 +23,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize...@@ -23,6 +23,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
23 import_table.use_llvm = false;23 import_table.use_llvm = false;
24 import_table.use_lld = false;24 import_table.use_lld = false;
25 import_table.import_table = true;25 import_table.import_table = true;
26 import_table.link_gc_sections = false;
2627
27 const export_table = b.addExecutable(.{28 const export_table = b.addExecutable(.{
28 .name = "export_table",29 .name = "export_table",
...@@ -34,6 +35,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize...@@ -34,6 +35,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
34 export_table.use_llvm = false;35 export_table.use_llvm = false;
35 export_table.use_lld = false;36 export_table.use_lld = false;
36 export_table.export_table = true;37 export_table.export_table = true;
38 export_table.link_gc_sections = false;
3739
38 const regular_table = b.addExecutable(.{40 const regular_table = b.addExecutable(.{
39 .name = "regular_table",41 .name = "regular_table",
...@@ -44,6 +46,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize...@@ -44,6 +46,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
44 regular_table.entry = .disabled;46 regular_table.entry = .disabled;
45 regular_table.use_llvm = false;47 regular_table.use_llvm = false;
46 regular_table.use_lld = false;48 regular_table.use_lld = false;
49 regular_table.link_gc_sections = false; // Ensure function table is not empty
4750
48 const check_import = import_table.checkObject();51 const check_import = import_table.checkObject();
49 const check_export = export_table.checkObject();52 const check_export = export_table.checkObject();
test/link/wasm/segments/build.zig+1
...@@ -23,6 +23,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize...@@ -23,6 +23,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
23 lib.use_llvm = false;23 lib.use_llvm = false;
24 lib.use_lld = false;24 lib.use_lld = false;
25 lib.strip = false;25 lib.strip = false;
26 lib.link_gc_sections = false; // so data is not garbage collected and we can verify data section
26 b.installArtifact(lib);27 b.installArtifact(lib);
2728
28 const check_lib = lib.checkObject();29 const check_lib = lib.checkObject();
test/link/wasm/stack_pointer/build.zig+1
...@@ -24,6 +24,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize...@@ -24,6 +24,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
24 lib.use_lld = false;24 lib.use_lld = false;
25 lib.strip = false;25 lib.strip = false;
26 lib.stack_size = std.wasm.page_size * 2; // set an explicit stack size26 lib.stack_size = std.wasm.page_size * 2; // set an explicit stack size
27 lib.link_gc_sections = false;
27 b.installArtifact(lib);28 b.installArtifact(lib);
2829
29 const check_lib = lib.checkObject();30 const check_lib = lib.checkObject();