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) = .{},
110110/// Output function section where the key is the original
111111/// function index and the value is function.
112112/// This allows us to map multiple symbols to the same function.
113functions: std.AutoArrayHashMapUnmanaged(struct { file: ?u16, index: u32 }, std.wasm.Func) = .{},
113functions: std.AutoArrayHashMapUnmanaged(struct { file: ?u16, index: u32 }, struct { func: std.wasm.Func, sym_index: u32 }) = .{},
114114/// Output global section
115115wasm_globals: std.ArrayListUnmanaged(std.wasm.Global) = .{},
116116/// Memory section
......@@ -1242,6 +1242,14 @@ fn resolveLazySymbols(wasm: *Wasm) !void {
12421242 if (wasm.undefs.fetchSwapRemove(name_offset)) |kv| {
12431243 const loc = try wasm.createSyntheticSymbolOffset(name_offset, .global);
12441244 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 });
12451253 }
12461254 }
12471255 }
......@@ -1301,6 +1309,35 @@ pub fn deinit(wasm: *Wasm) void {
13011309 archive.deinit(gpa);
13021310 }
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
13041341 wasm.decls.deinit(gpa);
13051342 wasm.anon_decls.deinit(gpa);
13061343 wasm.atom_types.deinit(gpa);
......@@ -1313,9 +1350,6 @@ pub fn deinit(wasm: *Wasm) void {
13131350 wasm.symbol_atom.deinit(gpa);
13141351 wasm.export_names.deinit(gpa);
13151352 wasm.atoms.deinit(gpa);
1316 for (wasm.managed_atoms.items) |*managed_atom| {
1317 managed_atom.deinit(wasm);
1318 }
13191353 wasm.managed_atoms.deinit(gpa);
13201354 wasm.segments.deinit(gpa);
13211355 wasm.data_segments.deinit(gpa);
......@@ -1550,7 +1584,7 @@ fn getFunctionSignature(wasm: *const Wasm, loc: SymbolLoc) std.wasm.Type {
15501584 const ty_index = wasm.imports.get(loc).?.kind.function;
15511585 return wasm.func_types.items[ty_index];
15521586 }
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];
15541588}
15551589
15561590/// Lowers a constant typed value to a local symbol and atom.
......@@ -1973,10 +2007,16 @@ pub fn addTableFunction(wasm: *Wasm, symbol_index: u32) !void {
19732007/// Starts at offset 1, where the value `0` represents an unresolved function pointer
19742008/// or null-pointer
19752009fn mapFunctionTable(wasm: *Wasm) void {
1976 var it = wasm.function_table.valueIterator();
2010 var it = wasm.function_table.iterator();
19772011 var index: u32 = 1;
1978 while (it.next()) |value_ptr| : (index += 1) {
1979 value_ptr.* = index;
2012 while (it.next()) |entry| {
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 }
19802020 }
19812021
19822022 if (wasm.base.options.import_table or wasm.base.options.output_mode == .Obj) {
......@@ -2094,20 +2134,28 @@ const Kind = union(enum) {
20942134fn parseAtom(wasm: *Wasm, atom_index: Atom.Index, kind: Kind) !void {
20952135 const atom = wasm.getAtomPtr(atom_index);
20962136 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
20972145 const final_index: u32 = switch (kind) {
20982146 .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);
21002148 const type_index = wasm.atom_types.get(atom_index).?;
21012149 try wasm.functions.putNoClobber(
21022150 wasm.base.allocator,
21032151 .{ .file = null, .index = index },
2104 .{ .type_index = type_index },
2152 .{ .func = .{ .type_index = type_index }, .sym_index = atom.sym_index },
21052153 );
21062154 symbol.tag = .function;
21072155 symbol.index = index;
21082156
21092157 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);
21112159 try wasm.segments.append(wasm.base.allocator, .{
21122160 .alignment = atom.alignment,
21132161 .size = atom.size,
......@@ -2145,12 +2193,12 @@ fn parseAtom(wasm: *Wasm, atom_index: Atom.Index, kind: Kind) !void {
21452193 const index = gop.value_ptr.*;
21462194 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).?);
21492197 // segment info already exists, so free its memory
21502198 wasm.base.allocator.free(segment_name);
21512199 break :result index;
21522200 } else {
2153 const index = @as(u32, @intCast(wasm.segments.items.len));
2201 const index: u32 = @intCast(wasm.segments.items.len);
21542202 var flags: u32 = 0;
21552203 if (wasm.base.options.shared_memory) {
21562204 flags |= @intFromEnum(Segment.Flag.WASM_DATA_SEGMENT_IS_PASSIVE);
......@@ -2163,7 +2211,7 @@ fn parseAtom(wasm: *Wasm, atom_index: Atom.Index, kind: Kind) !void {
21632211 });
21642212 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());
21672215 try wasm.segment_info.put(wasm.base.allocator, index, segment_info);
21682216 symbol.index = info_index;
21692217 break :result index;
......@@ -2234,14 +2282,37 @@ fn allocateAtoms(wasm: *Wasm) !void {
22342282 while (true) {
22352283 const atom = wasm.getAtomPtr(atom_index);
22362284 const symbol_loc = atom.symbolLoc();
2237 if (wasm.code_section_index) |index| {
2238 if (index == entry.key_ptr.*) {
2239 if (!wasm.resolved_symbols.contains(symbol_loc)) {
2240 // only allocate resolved function body's.
2241 atom_index = atom.prev orelse break;
2242 continue;
2285 // Ensure we get the original symbol, so we verify the correct symbol on whether
2286 // it is dead or not and ensure an atom is removed when dead.
2287 // This is required as we may have parsed aliases into atoms.
2288 const sym = if (symbol_loc.file) |object_index| sym: {
2289 const object = wasm.objects.items[object_index];
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;
22432305 }
22442306 }
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;
22452316 }
22462317 offset = @intCast(atom.alignment.forward(offset));
22472318 atom.offset = offset;
......@@ -2262,8 +2333,10 @@ fn allocateAtoms(wasm: *Wasm) !void {
22622333fn allocateVirtualAddresses(wasm: *Wasm) void {
22632334 for (wasm.resolved_symbols.keys()) |loc| {
22642335 const symbol = loc.getSymbol(wasm);
2265 if (symbol.tag != .data) {
2266 continue; // only data symbols have virtual addresses
2336 if (symbol.tag != .data or symbol.isDead()) {
2337 // 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;
22672340 }
22682341 const atom_index = wasm.symbol_atom.get(loc) orelse {
22692342 // synthetic symbol that does not contain an atom
......@@ -2350,11 +2423,17 @@ fn setupInitFunctions(wasm: *Wasm) !void {
23502423 .file = @as(u16, @intCast(file_index)),
23512424 .priority = init_func.priority,
23522425 });
2426 try wasm.mark(.{ .index = init_func.symbol_index, .file = @intCast(file_index) });
23532427 }
23542428 }
23552429
23562430 // sort the initfunctions based on their priority
23572431 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 }
23582437}
23592438
23602439/// Generates an atom containing the global error set' size.
......@@ -2377,7 +2456,7 @@ fn setupErrorsLen(wasm: *Wasm) !void {
23772456 prev_atom.next = atom.next;
23782457 atom.prev = null;
23792458 }
2380 atom.deinit(wasm);
2459 atom.deinit(wasm.base.allocator);
23812460 break :blk index;
23822461 } else new_atom: {
23832462 const atom_index: Atom.Index = @intCast(wasm.managed_atoms.items.len);
......@@ -2422,7 +2501,7 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void {
24222501 // call constructors
24232502 for (wasm.init_funcs.items) |init_func_loc| {
24242503 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;
24262505 const ty = wasm.func_types.items[func.type_index];
24272506
24282507 // Call function by its function index
......@@ -2455,13 +2534,16 @@ fn createSyntheticFunction(
24552534 const loc = wasm.findGlobalSymbol(symbol_name) orelse
24562535 try wasm.createSyntheticSymbol(symbol_name, .function);
24572536 const symbol = loc.getSymbol(wasm);
2537 if (symbol.isDead()) {
2538 return;
2539 }
24582540 const ty_index = try wasm.putOrGetFuncType(func_ty);
24592541 // create function with above type
24602542 const func_index = wasm.imported_functions_count + @as(u32, @intCast(wasm.functions.count()));
24612543 try wasm.functions.putNoClobber(
24622544 wasm.base.allocator,
24632545 .{ .file = null, .index = func_index },
2464 .{ .type_index = ty_index },
2546 .{ .func = .{ .type_index = ty_index }, .sym_index = loc.index },
24652547 );
24662548 symbol.index = func_index;
24672549
......@@ -2477,6 +2559,7 @@ fn createSyntheticFunction(
24772559 .next = null,
24782560 .prev = null,
24792561 .code = function_body.moveToUnmanaged(),
2562 .original_offset = 0,
24802563 };
24812564 try wasm.appendAtomAtIndex(wasm.code_section_index.?, atom_index);
24822565 try wasm.symbol_atom.putNoClobber(wasm.base.allocator, loc, atom_index);
......@@ -2513,6 +2596,7 @@ pub fn createFunction(
25132596 .prev = null,
25142597 .code = function_body.moveToUnmanaged(),
25152598 .relocs = relocations.moveToUnmanaged(),
2599 .original_offset = 0,
25162600 };
25172601 const symbol = loc.getSymbol(wasm);
25182602 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN); // ensure function does not get exported
......@@ -2614,21 +2698,21 @@ fn setupImports(wasm: *Wasm) !void {
26142698 }
26152699
26162700 for (wasm.resolved_symbols.keys()) |symbol_loc| {
2617 if (symbol_loc.file == null) {
2701 const file_index = symbol_loc.file orelse {
26182702 // imports generated by Zig code are already in the `import` section
26192703 continue;
2620 }
2704 };
26212705
26222706 const symbol = symbol_loc.getSymbol(wasm);
2623 if (std.mem.eql(u8, symbol_loc.getName(wasm), "__indirect_function_table")) {
2624 continue;
2625 }
2626 if (!symbol.requiresImport()) {
2707 if (symbol.isDead() or
2708 !symbol.requiresImport() or
2709 std.mem.eql(u8, symbol_loc.getName(wasm), "__indirect_function_table"))
2710 {
26272711 continue;
26282712 }
26292713
26302714 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];
26322716 const import = object.findImport(symbol.tag.externalType(), symbol.index);
26332717
26342718 // We copy the import to a new import to ensure the names contain references
......@@ -2680,6 +2764,9 @@ fn setupImports(wasm: *Wasm) !void {
26802764/// Takes the global, function and table section from each linked object file
26812765/// and merges it into a single section for each.
26822766fn mergeSections(wasm: *Wasm) !void {
2767 var removed_duplicates = std.ArrayList(SymbolLoc).init(wasm.base.allocator);
2768 defer removed_duplicates.deinit();
2769
26832770 for (wasm.resolved_symbols.keys()) |sym_loc| {
26842771 if (sym_loc.file == null) {
26852772 // Zig code-generated symbols are already within the sections and do not
......@@ -2689,7 +2776,11 @@ fn mergeSections(wasm: *Wasm) !void {
26892776
26902777 const object = &wasm.objects.items[sym_loc.file.?];
26912778 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 {
26932784 // Skip undefined symbols as they go in the `import` section
26942785 // Also skip symbols that do not need to have a section merged.
26952786 continue;
......@@ -2703,9 +2794,20 @@ fn mergeSections(wasm: *Wasm) !void {
27032794 wasm.base.allocator,
27042795 .{ .file = sym_loc.file, .index = symbol.index },
27052796 );
2706 if (!gop.found_existing) {
2707 gop.value_ptr.* = object.functions[index];
2797 if (gop.found_existing) {
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;
27082809 }
2810 gop.value_ptr.* = .{ .func = object.functions[index], .sym_index = sym_loc.index };
27092811 symbol.index = @as(u32, @intCast(gop.index)) + wasm.imported_functions_count;
27102812 },
27112813 .global => {
......@@ -2722,6 +2824,11 @@ fn mergeSections(wasm: *Wasm) !void {
27222824 }
27232825 }
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
27252832 log.debug("Merged ({d}) functions", .{wasm.functions.count()});
27262833 log.debug("Merged ({d}) globals", .{wasm.wasm_globals.items.len});
27272834 log.debug("Merged ({d}) tables", .{wasm.tables.items.len});
......@@ -2745,8 +2852,8 @@ fn mergeTypes(wasm: *Wasm) !void {
27452852 }
27462853 const object = wasm.objects.items[sym_loc.file.?];
27472854 const symbol = object.symtable[sym_loc.index];
2748 if (symbol.tag != .function) {
2749 // Only functions have types
2855 if (symbol.tag != .function or symbol.isDead()) {
2856 // Only functions have types. Only retrieve the type of referenced functions.
27502857 continue;
27512858 }
27522859
......@@ -2757,7 +2864,7 @@ fn mergeTypes(wasm: *Wasm) !void {
27572864 import.kind.function = try wasm.putOrGetFuncType(original_type);
27582865 } else if (!dirty.contains(symbol.index)) {
27592866 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;
27612868 func.type_index = try wasm.putOrGetFuncType(object.func_types[func.type_index]);
27622869 dirty.putAssumeCapacityNoClobber(symbol.index, {});
27632870 }
......@@ -2980,14 +3087,14 @@ fn setupMemory(wasm: *Wasm) !void {
29803087/// From a given object's index and the index of the segment, returns the corresponding
29813088/// index of the segment within the final data section. When the segment does not yet
29823089/// 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 {
29843091 const object: Object = wasm.objects.items[object_index];
2985 const relocatable_data = object.relocatable_data[relocatable_index];
3092 const symbol = object.symtable[symbol_index];
29863093 const index = @as(u32, @intCast(wasm.segments.items.len));
29873094
2988 switch (relocatable_data.type) {
3095 switch (symbol.tag) {
29893096 .data => {
2990 const segment_info = object.segment_info[relocatable_data.index];
3097 const segment_info = object.segment_info[symbol.index];
29913098 const merge_segment = wasm.base.options.output_mode != .Obj;
29923099 const result = try wasm.data_segments.getOrPut(wasm.base.allocator, segment_info.outputName(merge_segment));
29933100 if (!result.found_existing) {
......@@ -3002,70 +3109,75 @@ pub fn getMatchingSegment(wasm: *Wasm, object_index: u16, relocatable_index: u32
30023109 .offset = 0,
30033110 .flags = flags,
30043111 });
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 });
30053117 return index;
30063118 } else return result.value_ptr.*;
30073119 },
3008 .code => return wasm.code_section_index orelse blk: {
3120 .function => return wasm.code_section_index orelse blk: {
30093121 wasm.code_section_index = index;
30103122 try wasm.appendDummySegment();
30113123 break :blk index;
30123124 },
3013 .debug => {
3014 const debug_name = object.getDebugName(relocatable_data);
3015 if (mem.eql(u8, debug_name, ".debug_info")) {
3125 .section => {
3126 const section_name = object.string_table.get(symbol.name);
3127 if (mem.eql(u8, section_name, ".debug_info")) {
30163128 return wasm.debug_info_index orelse blk: {
30173129 wasm.debug_info_index = index;
30183130 try wasm.appendDummySegment();
30193131 break :blk index;
30203132 };
3021 } else if (mem.eql(u8, debug_name, ".debug_line")) {
3133 } else if (mem.eql(u8, section_name, ".debug_line")) {
30223134 return wasm.debug_line_index orelse blk: {
30233135 wasm.debug_line_index = index;
30243136 try wasm.appendDummySegment();
30253137 break :blk index;
30263138 };
3027 } else if (mem.eql(u8, debug_name, ".debug_loc")) {
3139 } else if (mem.eql(u8, section_name, ".debug_loc")) {
30283140 return wasm.debug_loc_index orelse blk: {
30293141 wasm.debug_loc_index = index;
30303142 try wasm.appendDummySegment();
30313143 break :blk index;
30323144 };
3033 } else if (mem.eql(u8, debug_name, ".debug_ranges")) {
3145 } else if (mem.eql(u8, section_name, ".debug_ranges")) {
30343146 return wasm.debug_line_index orelse blk: {
30353147 wasm.debug_ranges_index = index;
30363148 try wasm.appendDummySegment();
30373149 break :blk index;
30383150 };
3039 } else if (mem.eql(u8, debug_name, ".debug_pubnames")) {
3151 } else if (mem.eql(u8, section_name, ".debug_pubnames")) {
30403152 return wasm.debug_pubnames_index orelse blk: {
30413153 wasm.debug_pubnames_index = index;
30423154 try wasm.appendDummySegment();
30433155 break :blk index;
30443156 };
3045 } else if (mem.eql(u8, debug_name, ".debug_pubtypes")) {
3157 } else if (mem.eql(u8, section_name, ".debug_pubtypes")) {
30463158 return wasm.debug_pubtypes_index orelse blk: {
30473159 wasm.debug_pubtypes_index = index;
30483160 try wasm.appendDummySegment();
30493161 break :blk index;
30503162 };
3051 } else if (mem.eql(u8, debug_name, ".debug_abbrev")) {
3163 } else if (mem.eql(u8, section_name, ".debug_abbrev")) {
30523164 return wasm.debug_abbrev_index orelse blk: {
30533165 wasm.debug_abbrev_index = index;
30543166 try wasm.appendDummySegment();
30553167 break :blk index;
30563168 };
3057 } else if (mem.eql(u8, debug_name, ".debug_str")) {
3169 } else if (mem.eql(u8, section_name, ".debug_str")) {
30583170 return wasm.debug_str_index orelse blk: {
30593171 wasm.debug_str_index = index;
30603172 try wasm.appendDummySegment();
30613173 break :blk index;
30623174 };
30633175 } else {
3064 log.warn("found unknown debug section '{s}'", .{debug_name});
3065 log.warn(" debug section will be skipped", .{});
3066 return null;
3176 log.warn("found unknown section '{s}'", .{section_name});
3177 return error.UnexpectedValue;
30673178 }
30683179 },
3180 else => unreachable,
30693181 }
30703182}
30713183
......@@ -3108,6 +3220,7 @@ pub fn getErrorTableSymbol(wasm: *Wasm) !u32 {
31083220 .virtual_address = undefined,
31093221 };
31103222 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
3223 symbol.mark();
31113224
31123225 try wasm.resolved_symbols.put(wasm.base.allocator, atom.symbolLoc(), {});
31133226
......@@ -3140,6 +3253,7 @@ fn populateErrorNameTable(wasm: *Wasm) !void {
31403253 .virtual_address = undefined,
31413254 };
31423255 names_symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
3256 names_symbol.mark();
31433257
31443258 log.debug("Populating error names", .{});
31453259
......@@ -3431,18 +3545,15 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
34313545
34323546 try wasm.setupInitFunctions();
34333547 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();
34403553 try wasm.allocateAtoms();
34413554 try wasm.setupMemory();
34423555 wasm.allocateVirtualAddresses();
34433556 wasm.mapFunctionTable();
3444 try wasm.mergeSections();
3445 try wasm.mergeTypes();
34463557 try wasm.initializeCallCtorsFunction();
34473558 try wasm.setupInitMemoryFunction();
34483559 try wasm.setupTLSRelocationsFunction();
......@@ -3519,8 +3630,9 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
35193630 // So we can rebuild the binary file on each incremental update
35203631 defer wasm.resetState();
35213632 try wasm.setupInitFunctions();
3522 try wasm.setupErrorsLen();
35233633 try wasm.setupStart();
3634 try wasm.markReferences();
3635 try wasm.setupErrorsLen();
35243636 try wasm.setupImports();
35253637 if (wasm.base.options.module) |mod| {
35263638 var decl_it = wasm.decls.iterator();
......@@ -3577,16 +3689,12 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
35773689 }
35783690 }
35793691
3580 for (wasm.objects.items, 0..) |*object, object_index| {
3581 try object.parseIntoAtoms(wasm.base.allocator, @as(u16, @intCast(object_index)), wasm);
3582 }
3583
3692 try wasm.mergeSections();
3693 try wasm.mergeTypes();
35843694 try wasm.allocateAtoms();
35853695 try wasm.setupMemory();
35863696 wasm.allocateVirtualAddresses();
35873697 wasm.mapFunctionTable();
3588 try wasm.mergeSections();
3589 try wasm.mergeTypes();
35903698 try wasm.initializeCallCtorsFunction();
35913699 try wasm.setupInitMemoryFunction();
35923700 try wasm.setupTLSRelocationsFunction();
......@@ -3644,8 +3752,8 @@ fn writeToFile(
36443752 binary_bytes.items,
36453753 header_offset,
36463754 .type,
3647 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),
3648 @as(u32, @intCast(wasm.func_types.items.len)),
3755 @intCast(binary_bytes.items.len - header_offset - header_size),
3756 @intCast(wasm.func_types.items.len),
36493757 );
36503758 section_count += 1;
36513759 }
......@@ -3677,8 +3785,8 @@ fn writeToFile(
36773785 binary_bytes.items,
36783786 header_offset,
36793787 .import,
3680 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),
3681 @as(u32, @intCast(wasm.imports.count() + @intFromBool(import_memory))),
3788 @intCast(binary_bytes.items.len - header_offset - header_size),
3789 @intCast(wasm.imports.count() + @intFromBool(import_memory)),
36823790 );
36833791 section_count += 1;
36843792 }
......@@ -3687,15 +3795,15 @@ fn writeToFile(
36873795 if (wasm.functions.count() != 0) {
36883796 const header_offset = try reserveVecSectionHeader(&binary_bytes);
36893797 for (wasm.functions.values()) |function| {
3690 try leb.writeULEB128(binary_writer, function.type_index);
3798 try leb.writeULEB128(binary_writer, function.func.type_index);
36913799 }
36923800
36933801 try writeVecSectionHeader(
36943802 binary_bytes.items,
36953803 header_offset,
36963804 .function,
3697 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),
3698 @as(u32, @intCast(wasm.functions.count())),
3805 @intCast(binary_bytes.items.len - header_offset - header_size),
3806 @intCast(wasm.functions.count()),
36993807 );
37003808 section_count += 1;
37013809 }
......@@ -3713,8 +3821,8 @@ fn writeToFile(
37133821 binary_bytes.items,
37143822 header_offset,
37153823 .table,
3716 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),
3717 @as(u32, @intCast(wasm.tables.items.len)),
3824 @intCast(binary_bytes.items.len - header_offset - header_size),
3825 @intCast(wasm.tables.items.len),
37183826 );
37193827 section_count += 1;
37203828 }
......@@ -3728,8 +3836,8 @@ fn writeToFile(
37283836 binary_bytes.items,
37293837 header_offset,
37303838 .memory,
3731 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),
3732 @as(u32, 1), // wasm currently only supports 1 linear memory segment
3839 @intCast(binary_bytes.items.len - header_offset - header_size),
3840 1, // wasm currently only supports 1 linear memory segment
37333841 );
37343842 section_count += 1;
37353843 }
......@@ -3748,8 +3856,8 @@ fn writeToFile(
37483856 binary_bytes.items,
37493857 header_offset,
37503858 .global,
3751 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),
3752 @as(u32, @intCast(wasm.wasm_globals.items.len)),
3859 @intCast(binary_bytes.items.len - header_offset - header_size),
3860 @intCast(wasm.wasm_globals.items.len),
37533861 );
37543862 section_count += 1;
37553863 }
......@@ -3777,8 +3885,8 @@ fn writeToFile(
37773885 binary_bytes.items,
37783886 header_offset,
37793887 .@"export",
3780 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),
3781 @as(u32, @intCast(wasm.exports.items.len)) + @intFromBool(export_memory),
3888 @intCast(binary_bytes.items.len - header_offset - header_size),
3889 @intCast(wasm.exports.items.len + @intFromBool(export_memory)),
37823890 );
37833891 section_count += 1;
37843892 }
......@@ -3813,15 +3921,16 @@ fn writeToFile(
38133921 try leb.writeULEB128(binary_writer, @as(u32, @intCast(wasm.function_table.count())));
38143922 var symbol_it = wasm.function_table.keyIterator();
38153923 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);
38173926 }
38183927
38193928 try writeVecSectionHeader(
38203929 binary_bytes.items,
38213930 header_offset,
38223931 .element,
3823 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),
3824 @as(u32, 1),
3932 @intCast(binary_bytes.items.len - header_offset - header_size),
3933 1,
38253934 );
38263935 section_count += 1;
38273936 }
......@@ -3834,8 +3943,8 @@ fn writeToFile(
38343943 binary_bytes.items,
38353944 header_offset,
38363945 .data_count,
3837 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),
3838 @as(u32, @intCast(data_segments_count)),
3946 @intCast(binary_bytes.items.len - header_offset - header_size),
3947 @intCast(data_segments_count),
38393948 );
38403949 }
38413950
......@@ -3846,20 +3955,18 @@ fn writeToFile(
38463955 var atom_index = wasm.atoms.get(code_index).?;
38473956
38483957 // 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());
38503959 defer sorted_atoms.deinit();
38513960
38523961 while (true) {
3853 var atom = wasm.getAtomPtr(atom_index);
3854 if (wasm.resolved_symbols.contains(atom.symbolLoc())) {
3855 if (!is_obj) {
3856 atom.resolveRelocs(wasm);
3857 }
3858 sorted_atoms.appendAssumeCapacity(atom);
3962 const atom = wasm.getAtomPtr(atom_index);
3963 if (!is_obj) {
3964 atom.resolveRelocs(wasm);
38593965 }
3860 // atom = if (atom.prev) |prev| wasm.getAtomPtr(prev) else break;
3966 sorted_atoms.appendAssumeCapacity(atom); // found more code atoms than functions
38613967 atom_index = atom.prev orelse break;
38623968 }
3969 std.debug.assert(wasm.functions.count() == sorted_atoms.items.len);
38633970
38643971 const atom_sort_fn = struct {
38653972 fn sort(ctx: *const Wasm, lhs: *const Atom, rhs: *const Atom) bool {
......@@ -3869,7 +3976,7 @@ fn writeToFile(
38693976 }
38703977 }.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
38743981 for (sorted_atoms.items) |sorted_atom| {
38753982 try leb.writeULEB128(binary_writer, sorted_atom.size);
......@@ -3882,7 +3989,7 @@ fn writeToFile(
38823989 header_offset,
38833990 .code,
38843991 code_section_size,
3885 @as(u32, @intCast(wasm.functions.count())),
3992 @intCast(wasm.functions.count()),
38863993 );
38873994 code_section_index = section_count;
38883995 section_count += 1;
......@@ -3953,8 +4060,8 @@ fn writeToFile(
39534060 binary_bytes.items,
39544061 header_offset,
39554062 .data,
3956 @as(u32, @intCast(binary_bytes.items.len - header_offset - header_size)),
3957 @as(u32, @intCast(segment_count)),
4063 @intCast(binary_bytes.items.len - header_offset - header_size),
4064 @intCast(segment_count),
39584065 );
39594066 data_section_index = section_count;
39604067 section_count += 1;
......@@ -4210,6 +4317,9 @@ fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem
42104317
42114318 for (wasm.resolved_symbols.keys()) |sym_loc| {
42124319 const symbol = sym_loc.getSymbol(wasm).*;
4320 if (symbol.isDead()) {
4321 continue;
4322 }
42134323 const name = sym_loc.getName(wasm);
42144324 switch (symbol.tag) {
42154325 .function => {
......@@ -4498,6 +4608,14 @@ fn linkWithLLD(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !
44984608 try argv.append("--export-table");
44994609 }
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
45014619 if (wasm.base.options.strip) {
45024620 try argv.append("-s");
45034621 }
......@@ -4783,7 +4901,7 @@ fn emitLinkSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), symbol_table:
47834901 try wasm.emitSymbolTable(binary_bytes, symbol_table);
47844902 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);
47874905 try writeCustomSectionHeader(binary_bytes.items, offset, size);
47884906}
47894907
......@@ -4831,7 +4949,7 @@ fn emitSymbolTable(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), symbol_table:
48314949 }
48324950
48334951 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));
48354953 leb.writeUnsignedFixed(5, buf[5..], symbol_count);
48364954 try binary_bytes.insertSlice(table_offset, &buf);
48374955}
......@@ -4914,7 +5032,7 @@ fn emitCodeRelocations(
49145032 var buf: [5]u8 = undefined;
49155033 leb.writeUnsignedFixed(5, &buf, count);
49165034 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);
49185036 try writeCustomSectionHeader(binary_bytes.items, header_offset, size);
49195037}
49205038
......@@ -5018,3 +5136,67 @@ pub fn storeDeclType(wasm: *Wasm, decl_index: InternPool.DeclIndex, func_type: s
50185136 try wasm.atom_types.put(wasm.base.allocator, atom_index, index);
50195137 return index;
50205138}
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,
2323/// Offset into the section where the atom lives, this already accounts
2424/// for alignment.
2525offset: 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
2630/// Represents the index of the file this atom was generated from.
2731/// This is 'null' when the atom was generated by a Decl from Zig code.
2832file: ?u16,
......@@ -50,11 +54,11 @@ pub const empty: Atom = .{
5054 .prev = null,
5155 .size = 0,
5256 .sym_index = 0,
57 .original_offset = 0,
5358};
5459
5560/// Frees all resources owned by this `Atom`.
56pub fn deinit(atom: *Atom, wasm: *Wasm) void {
57 const gpa = wasm.base.allocator;
61pub fn deinit(atom: *Atom, gpa: std.mem.Allocator) void {
5862 atom.relocs.deinit(gpa);
5963 atom.code.deinit(gpa);
6064 atom.locals.deinit(gpa);
......@@ -114,10 +118,10 @@ pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {
114118 .R_WASM_GLOBAL_INDEX_I32,
115119 .R_WASM_MEMORY_ADDR_I32,
116120 .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),
118122 .R_WASM_TABLE_INDEX_I64,
119123 .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),
121125 .R_WASM_GLOBAL_INDEX_LEB,
122126 .R_WASM_EVENT_INDEX_LEB,
123127 .R_WASM_FUNCTION_INDEX_LEB,
......@@ -127,12 +131,12 @@ pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {
127131 .R_WASM_TABLE_NUMBER_LEB,
128132 .R_WASM_TYPE_INDEX_LEB,
129133 .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))),
131135 .R_WASM_MEMORY_ADDR_LEB64,
132136 .R_WASM_MEMORY_ADDR_SLEB64,
133137 .R_WASM_TABLE_INDEX_SLEB64,
134138 .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),
136140 }
137141 }
138142}
......@@ -150,7 +154,7 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa
150154 .R_WASM_TABLE_INDEX_I64,
151155 .R_WASM_TABLE_INDEX_SLEB,
152156 .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,
154158 .R_WASM_TYPE_INDEX_LEB => {
155159 const file_index = atom.file orelse {
156160 return relocation.index;
src/link/Wasm/Object.zig+122-142
......@@ -59,20 +59,16 @@ init_funcs: []const types.InitFunc = &.{},
5959comdat_info: []const types.Comdat = &.{},
6060/// Represents non-synthetic sections that can essentially be mem-cpy'd into place
6161/// after performing relocations.
62relocatable_data: []const RelocatableData = &.{},
62relocatable_data: std.AutoHashMapUnmanaged(RelocatableData.Tag, []RelocatableData) = .{},
6363/// String table for all strings required by the object file, such as symbol names,
6464/// import name, module name and export names. Each string will be deduplicated
6565/// and returns an offset into the table.
6666string_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
7268/// Represents a single item within a section (depending on its `type`)
7369const RelocatableData = struct {
7470 /// The type of the relocatable data
75 type: enum { data, code, debug },
71 type: Tag,
7672 /// Pointer to the data of the segment, where its length is written to `size`
7773 data: [*]u8,
7874 /// The size in bytes of the data representing the segment within the section
......@@ -85,6 +81,8 @@ const RelocatableData = struct {
8581 /// Represents the index of the section it belongs to
8682 section_index: u32,
8783
84 const Tag = enum { data, code, custom };
85
8886 /// Returns the alignment of the segment, by retrieving it from the segment
8987 /// meta data of the given object file.
9088 /// NOTE: Alignment is encoded as a power of 2, so we shift the symbol's
......@@ -99,14 +97,14 @@ const RelocatableData = struct {
9997 return switch (relocatable_data.type) {
10098 .data => .data,
10199 .code => .function,
102 .debug => .section,
100 .custom => .section,
103101 };
104102 }
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,
107105 /// returns the section index within the object file.
108106 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;
110108 return relocatable_data.index;
111109 }
112110};
......@@ -121,7 +119,6 @@ pub fn create(gpa: Allocator, file: std.fs.File, name: []const u8, maybe_max_siz
121119 var object: Object = .{
122120 .file = file,
123121 .name = try gpa.dupe(u8, name),
124 .debug_names = &.{},
125122 };
126123
127124 var is_object_file: bool = false;
......@@ -182,10 +179,16 @@ pub fn deinit(object: *Object, gpa: Allocator) void {
182179 gpa.free(info.name);
183180 }
184181 gpa.free(object.segment_info);
185 for (object.relocatable_data) |rel_data| {
186 gpa.free(rel_data.data[0..rel_data.size]);
182 {
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 }
187190 }
188 gpa.free(object.relocatable_data);
191 object.relocatable_data.deinit(gpa);
189192 object.string_table.deinit(gpa);
190193 gpa.free(object.name);
191194 object.* = undefined;
......@@ -345,23 +348,7 @@ fn Parser(comptime ReaderType: type) type {
345348 errdefer parser.object.deinit(gpa);
346349 try parser.verifyMagicBytes();
347350 const version = try parser.reader.reader().readInt(u32, .little);
348
349351 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
366353 var section_index: u32 = 0;
367354 while (parser.reader.reader().readByte()) |byte| : (section_index += 1) {
......@@ -377,26 +364,34 @@ fn Parser(comptime ReaderType: type) type {
377364
378365 if (std.mem.eql(u8, name, "linking")) {
379366 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.
381367 try parser.parseMetadata(gpa, @as(usize, @intCast(reader.context.bytes_left)));
382368 } else if (std.mem.startsWith(u8, name, "reloc")) {
383369 try parser.parseRelocations(gpa);
384370 } else if (std.mem.eql(u8, name, "target_features")) {
385371 try parser.parseFeatures(gpa);
386372 } 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 }
387381 const debug_size = @as(u32, @intCast(reader.context.bytes_left));
388382 const debug_content = try gpa.alloc(u8, debug_size);
389383 errdefer gpa.free(debug_content);
390384 try reader.readNoEof(debug_content);
391385
392 try relocatable_data.append(.{
393 .type = .debug,
386 try relocatable_data.append(gpa, .{
387 .type = .custom,
394388 .data = debug_content.ptr,
395389 .size = debug_size,
396390 .index = try parser.object.string_table.put(gpa, name),
397391 .offset = 0, // debug sections only contain 1 entry, so no need to calculate offset
398392 .section_index = section_index,
399393 });
394 gop.value_ptr.* = try relocatable_data.toOwnedSlice(gpa);
400395 } else {
401396 try reader.skipBytes(reader.context.bytes_left, .{});
402397 }
......@@ -515,26 +510,32 @@ fn Parser(comptime ReaderType: type) type {
515510 const start = reader.context.bytes_left;
516511 var index: u32 = 0;
517512 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();
518516 while (index < count) : (index += 1) {
519517 const code_len = try readLeb(u32, reader);
520518 const offset = @as(u32, @intCast(start - reader.context.bytes_left));
521519 const data = try gpa.alloc(u8, code_len);
522520 errdefer gpa.free(data);
523521 try reader.readNoEof(data);
524 try relocatable_data.append(.{
522 relocatable_data.appendAssumeCapacity(.{
525523 .type = .code,
526524 .data = data.ptr,
527525 .size = code_len,
528 .index = parser.object.importedCountByKind(.function) + index,
526 .index = imported_function_count + index,
529527 .offset = offset,
530528 .section_index = section_index,
531529 });
532530 }
531 try parser.object.relocatable_data.put(gpa, .code, try relocatable_data.toOwnedSlice());
533532 },
534533 .data => {
535534 const start = reader.context.bytes_left;
536535 var index: u32 = 0;
537536 const count = try readLeb(u32, reader);
537 var relocatable_data = try std.ArrayList(RelocatableData).initCapacity(gpa, count);
538 defer relocatable_data.deinit();
538539 while (index < count) : (index += 1) {
539540 const flags = try readLeb(u32, reader);
540541 const data_offset = try readInit(reader);
......@@ -545,7 +546,7 @@ fn Parser(comptime ReaderType: type) type {
545546 const data = try gpa.alloc(u8, data_len);
546547 errdefer gpa.free(data);
547548 try reader.readNoEof(data);
548 try relocatable_data.append(.{
549 relocatable_data.appendAssumeCapacity(.{
549550 .type = .data,
550551 .data = data.ptr,
551552 .size = data_len,
......@@ -554,6 +555,7 @@ fn Parser(comptime ReaderType: type) type {
554555 .section_index = section_index,
555556 });
556557 }
558 try parser.object.relocatable_data.put(gpa, .data, try relocatable_data.toOwnedSlice());
557559 },
558560 else => try parser.reader.reader().skipBytes(len, .{}),
559561 }
......@@ -561,7 +563,6 @@ fn Parser(comptime ReaderType: type) type {
561563 error.EndOfStream => {}, // finished parsing the file
562564 else => |e| return e,
563565 }
564 parser.object.relocatable_data = try relocatable_data.toOwnedSlice();
565566 }
566567
567568 /// Based on the "features" custom section, parses it into a list of
......@@ -789,7 +790,8 @@ fn Parser(comptime ReaderType: type) type {
789790 },
790791 .section => {
791792 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| {
793795 if (data.section_index == symbol.index) {
794796 symbol.name = data.index;
795797 break;
......@@ -798,22 +800,15 @@ fn Parser(comptime ReaderType: type) type {
798800 },
799801 else => {
800802 symbol.index = try leb.readULEB128(u32, reader);
801 var maybe_import: ?types.Import = null;
802
803803 const is_undefined = symbol.isUndefined();
804 if (is_undefined) {
805 maybe_import = parser.object.findImport(symbol.tag.externalType(), symbol.index);
806 }
807804 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: {
809806 const name_len = try leb.readULEB128(u32, reader);
810807 const name = try gpa.alloc(u8, name_len);
811808 defer gpa.free(name);
812809 try reader.readNoEof(name);
813 symbol.name = try parser.object.string_table.put(gpa, name);
814 } else {
815 symbol.name = maybe_import.?.name;
816 }
810 break :name try parser.object.string_table.put(gpa, name);
811 } else parser.object.findImport(symbol.tag.externalType(), symbol.index).name;
817812 },
818813 }
819814 return symbol;
......@@ -887,110 +882,95 @@ fn assertEnd(reader: anytype) !void {
887882}
888883
889884/// 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 {
891 const Key = struct {
892 kind: Symbol.Tag,
893 index: u32,
894 };
895 var symbol_for_segment = std.AutoArrayHashMap(Key, std.ArrayList(u32)).init(gpa);
896 defer for (symbol_for_segment.values()) |*list| {
897 list.deinit();
898 } else symbol_for_segment.deinit();
899
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);
885pub fn parseSymbolIntoAtom(object: *Object, object_index: u16, symbol_index: u32, wasm: *Wasm) !Atom.Index {
886 const symbol = &object.symtable[symbol_index];
887 const relocatable_data: RelocatableData = switch (symbol.tag) {
888 .function => object.relocatable_data.get(.code).?[symbol.index - object.importedCountByKind(.function)],
889 .data => object.relocatable_data.get(.data).?[symbol.index],
890 .section => blk: {
891 const data = object.relocatable_data.get(.custom).?;
892 for (data) |dat| {
893 if (dat.section_index == symbol.index) {
894 break :blk dat;
907895 }
908 try gop.value_ptr.*.append(sym_idx);
909 },
910 else => continue,
911 }
896 }
897 unreachable;
898 },
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);
912917 }
913918
914 for (object.relocatable_data, 0..) |relocatable_data, index| {
915 const final_index = (try wasm_bin.getMatchingSegment(object_index, @as(u32, @intCast(index)))) orelse {
916 continue; // found unknown section, so skip parsing into atom as we do not know how to handle it.
917 };
918
919 const atom_index: Atom.Index = @intCast(wasm_bin.managed_atoms.items.len);
920 const atom = try wasm_bin.managed_atoms.addOne(gpa);
921 atom.* = Atom.empty;
922 atom.file = object_index;
923 atom.size = relocatable_data.size;
924 atom.alignment = relocatable_data.getAlignment(object);
925
926 const relocations: []types.Relocation = object.relocations.get(relocatable_data.section_index) orelse &.{};
927 for (relocations) |relocation| {
928 if (isInbetween(relocatable_data.offset, atom.size, relocation.offset)) {
929 // set the offset relative to the offset of the segment itobject,
930 // rather than within the entire section.
931 var reloc = relocation;
932 reloc.offset -= relocatable_data.offset;
933 try atom.relocs.append(gpa, reloc);
934
935 switch (relocation.relocation_type) {
936 .R_WASM_TABLE_INDEX_I32,
937 .R_WASM_TABLE_INDEX_I64,
938 .R_WASM_TABLE_INDEX_SLEB,
939 .R_WASM_TABLE_INDEX_SLEB64,
940 => {
941 try wasm_bin.function_table.put(gpa, .{
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 }
919 if (object.relocations.get(relocatable_data.section_index)) |relocations| {
920 const start = searchRelocStart(relocations, relocatable_data.offset);
921 const len = searchRelocEnd(relocations[start..], relocatable_data.offset + atom.size);
922 atom.relocs = std.ArrayListUnmanaged(types.Relocation).fromOwnedSlice(relocations[start..][0..len]);
923 for (atom.relocs.items) |reloc| {
924 switch (reloc.relocation_type) {
925 .R_WASM_TABLE_INDEX_I32,
926 .R_WASM_TABLE_INDEX_I64,
927 .R_WASM_TABLE_INDEX_SLEB,
928 .R_WASM_TABLE_INDEX_SLEB64,
929 => {
930 try wasm.function_table.put(wasm.base.allocator, .{
931 .file = object_index,
932 .index = reloc.index,
933 }, 0);
934 },
935 .R_WASM_GLOBAL_INDEX_I32,
936 .R_WASM_GLOBAL_INDEX_LEB,
937 => {
938 const sym = object.symtable[reloc.index];
939 if (sym.tag != .global) {
940 try wasm.got_symbols.append(
941 wasm.base.allocator,
942 .{ .file = object_index, .index = reloc.index },
943 );
944 }
945 },
946 else => {},
959947 }
960948 }
949 }
961950
962 try atom.code.appendSlice(gpa, relocatable_data.data[0..relocatable_data.size]);
963
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 }
951 return atom_index;
952}
981953
982 const segment: *Wasm.Segment = &wasm_bin.segments.items[final_index];
983 if (relocatable_data.type == .data) { //code section and debug sections are 1-byte aligned
984 segment.alignment = segment.alignment.max(atom.alignment);
954fn searchRelocStart(relocs: []const types.Relocation, address: u32) usize {
955 var min: usize = 0;
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;
985964 }
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 });
989965 }
966 return min;
990967}
991968
992/// Verifies if a given value is in between a minimum -and maximum value.
993/// The maxmimum value is calculated using the length, both start and end are inclusive.
994inline fn isInbetween(min: u32, length: u32, value: u32) bool {
995 return value >= min and value <= min + length;
969fn searchRelocEnd(relocs: []const types.Relocation, address: u32) usize {
970 for (relocs, 0..relocs.len) |reloc, index| {
971 if (reloc.offset > address) {
972 return index;
973 }
974 }
975 return relocs.len;
996976}
src/link/Wasm/Symbol.zig+20
......@@ -79,6 +79,9 @@ pub const Flag = enum(u32) {
7979 WASM_SYM_NO_STRIP = 0x80,
8080 /// Indicates a symbol is TLS
8181 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,
8285};
8386
8487/// Verifies if the given symbol should be imported from the
......@@ -92,6 +95,23 @@ pub fn requiresImport(symbol: Symbol) bool {
9295 return true;
9396}
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
95115pub fn isTLS(symbol: Symbol) bool {
96116 return symbol.flags & @intFromEnum(Flag.WASM_SYM_TLS) != 0;
97117}
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
2626 lib.strip = false;
2727 // to make sure the bss segment is emitted, we must import memory
2828 lib.import_memory = true;
29 lib.link_gc_sections = false;
2930
3031 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
7374 lib.strip = false;
7475 // to make sure the bss segment is emitted, we must import memory
7576 lib.import_memory = true;
77 lib.link_gc_sections = false;
7678
7779 const check_lib = lib.checkObject();
7880 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
2323 import_table.use_llvm = false;
2424 import_table.use_lld = false;
2525 import_table.import_table = true;
26 import_table.link_gc_sections = false;
2627
2728 const export_table = b.addExecutable(.{
2829 .name = "export_table",
......@@ -34,6 +35,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
3435 export_table.use_llvm = false;
3536 export_table.use_lld = false;
3637 export_table.export_table = true;
38 export_table.link_gc_sections = false;
3739
3840 const regular_table = b.addExecutable(.{
3941 .name = "regular_table",
......@@ -44,6 +46,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
4446 regular_table.entry = .disabled;
4547 regular_table.use_llvm = false;
4648 regular_table.use_lld = false;
49 regular_table.link_gc_sections = false; // Ensure function table is not empty
4750
4851 const check_import = import_table.checkObject();
4952 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
2323 lib.use_llvm = false;
2424 lib.use_lld = false;
2525 lib.strip = false;
26 lib.link_gc_sections = false; // so data is not garbage collected and we can verify data section
2627 b.installArtifact(lib);
2728
2829 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
2424 lib.use_lld = false;
2525 lib.strip = false;
2626 lib.stack_size = std.wasm.page_size * 2; // set an explicit stack size
27 lib.link_gc_sections = false;
2728 b.installArtifact(lib);
2829
2930 const check_lib = lib.checkObject();