authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-01-12 09:43:31+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-01-12 20:50:18+01:00
logc77ca9174976a9fb8769276ce913e761d66af1de
tree94d02f581f1b8b6f0665aed3282ef4bbcb8c258b
parentf8d1efd99ab0ff9ae49a17b437814f4fe329e83b
signature Commit is signed but in an unrecognized format.

wasm-linker: implement `__heap_base` symbol

When any object files provides an undefined reference to the __heap_base symbol, we create a new defined symbol for it. During setupMemory we set the virtual address of this symbol so it can be used for relocations. This symbol represents where the heap starts and allocators can use this value for its allocations when it needs to determine where the heap lives.

2 files changed, 79 insertions(+), 34 deletions(-)

src/link/Wasm.zig+78-34
......@@ -475,7 +475,7 @@ fn createSyntheticSymbol(wasm: *Wasm, name: []const u8, tag: Symbol.Tag) !Symbol
475475 .index = undefined,
476476 });
477477 try wasm.resolved_symbols.putNoClobber(wasm.base.allocator, loc, {});
478 try wasm.globals.putNoClobber(wasm.base.allocator, name_offset, loc);
478 try wasm.globals.put(wasm.base.allocator, name_offset, loc);
479479 return loc;
480480}
481481/// Initializes symbols and atoms for the debug sections
......@@ -851,6 +851,35 @@ fn validateFeatures(
851851 to_emit.* = allowed;
852852}
853853
854/// Creates synthetic linker-symbols, but only if they are being referenced from
855/// any object file. For instance, the `__heap_base` symbol will only be created,
856/// if one or multiple undefined references exist. When none exist, the symbol will
857/// not be created, ensuring we don't unneccesarily emit unreferenced symbols.
858fn resolveLazySymbols(wasm: *Wasm) !void {
859 if (wasm.undefs.fetchSwapRemove("__heap_base")) |kv| {
860 const loc = try wasm.createSyntheticSymbol("__heap_base", .data);
861 try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc);
862 _ = wasm.resolved_symbols.swapRemove(loc); // we don't want to emit this symbol, only use it for relocations.
863
864 const atom = try wasm.base.allocator.create(Atom);
865 errdefer wasm.base.allocator.destroy(atom);
866 try wasm.managed_atoms.append(wasm.base.allocator, atom);
867 atom.* = Atom.empty;
868 atom.sym_index = loc.index;
869 atom.alignment = 1;
870
871 try wasm.parseAtom(atom, .{ .data = .synthetic });
872 try wasm.symbol_atom.putNoClobber(wasm.base.allocator, loc, atom);
873 }
874}
875
876// Tries to find a global symbol by its name. Returns null when not found,
877/// and its location when it is found.
878fn findGlobalSymbol(wasm: *Wasm, name: []const u8) ?SymbolLoc {
879 const offset = wasm.string_table.getOffset(name) orelse return null;
880 return wasm.globals.get(offset);
881}
882
854883fn checkUndefinedSymbols(wasm: *const Wasm) !void {
855884 if (wasm.base.options.output_mode == .Obj) return;
856885 if (wasm.base.options.import_symbols) return;
......@@ -1458,14 +1487,13 @@ fn mapFunctionTable(wasm: *Wasm) void {
14581487 }
14591488
14601489 if (wasm.base.options.import_table or wasm.base.options.output_mode == .Obj) {
1461 const sym_loc = wasm.globals.get(wasm.string_table.getOffset("__indirect_function_table").?).?;
1490 const sym_loc = wasm.findGlobalSymbol("__indirect_function_table").?;
14621491 const import = wasm.imports.getPtr(sym_loc).?;
14631492 import.kind.table.limits.min = index - 1; // we start at index 1.
14641493 } else if (index > 1) {
14651494 log.debug("Appending indirect function table", .{});
1466 const offset = wasm.string_table.getOffset("__indirect_function_table").?;
1467 const sym_with_loc = wasm.globals.get(offset).?;
1468 const symbol = sym_with_loc.getSymbol(wasm);
1495 const sym_loc = wasm.findGlobalSymbol("__indirect_function_table").?;
1496 const symbol = sym_loc.getSymbol(wasm);
14691497 const table = &wasm.tables.items[symbol.index - wasm.imported_tables_count];
14701498 table.limits = .{ .min = index, .max = index };
14711499 }
......@@ -1544,6 +1572,7 @@ const Kind = union(enum) {
15441572 read_only,
15451573 uninitialized,
15461574 initialized,
1575 synthetic,
15471576 },
15481577 function: FnData,
15491578
......@@ -1554,6 +1583,7 @@ const Kind = union(enum) {
15541583 .read_only => return ".rodata.",
15551584 .uninitialized => return ".bss.",
15561585 .initialized => return ".data.",
1586 .synthetic => return ".synthetic",
15571587 }
15581588 }
15591589};
......@@ -1690,9 +1720,14 @@ fn allocateAtoms(wasm: *Wasm) !void {
16901720 var offset: u32 = 0;
16911721 while (true) {
16921722 const symbol_loc = atom.symbolLoc();
1693 if (!wasm.resolved_symbols.contains(symbol_loc)) {
1694 atom = atom.next orelse break;
1695 continue;
1723 if (wasm.code_section_index) |index| {
1724 if (index == entry.key_ptr.*) {
1725 if (!wasm.resolved_symbols.contains(symbol_loc)) {
1726 // only allocate resolved function body's.
1727 atom = atom.next orelse break;
1728 continue;
1729 }
1730 }
16961731 }
16971732 offset = std.mem.alignForwardGeneric(u32, offset, atom.alignment);
16981733 atom.offset = offset;
......@@ -1727,6 +1762,7 @@ fn sortDataSegments(wasm: *Wasm) !void {
17271762 if (mem.startsWith(u8, name, ".rodata")) return 0;
17281763 if (mem.startsWith(u8, name, ".data")) return 1;
17291764 if (mem.startsWith(u8, name, ".text")) return 2;
1765 if (mem.startsWith(u8, name, ".synthetic")) return 100; // always at end
17301766 return 3;
17311767 }
17321768 };
......@@ -1789,7 +1825,7 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void {
17891825 if (wasm.code_section_index == null) {
17901826 // Make sure to remove it from the resolved symbols so we do not emit
17911827 // it within any section. TODO: Remove this once we implement garbage collection.
1792 const loc = wasm.globals.get(wasm.string_table.getOffset("__wasm_call_ctors").?).?;
1828 const loc = wasm.findGlobalSymbol("__wasm_call_ctors").?;
17931829 std.debug.assert(wasm.resolved_symbols.swapRemove(loc));
17941830 return;
17951831 }
......@@ -1806,11 +1842,6 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void {
18061842 // call constructors
18071843 for (wasm.init_funcs.items) |init_func_loc| {
18081844 const symbol = init_func_loc.getSymbol(wasm);
1809 if (symbol.isUndefined()) {
1810 std.debug.print("Undefined symbol '{s}'\n", .{wasm.string_table.get(symbol.name)});
1811 }
1812 std.debug.print("Symbol: {s}\n", .{init_func_loc.getSymbolLoc().getName(wasm)});
1813 std.debug.assert(wasm.resolved_symbols.contains(init_func_loc.getSymbolLoc().finalLoc(wasm)));
18141845 const func = wasm.functions.values()[symbol.index - wasm.imported_functions_count];
18151846 const ty = wasm.func_types.items[func.type_index];
18161847
......@@ -1828,7 +1859,7 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void {
18281859 try writer.writeByte(std.wasm.opcode(.end));
18291860 }
18301861
1831 const loc = wasm.globals.get(wasm.string_table.getOffset("__wasm_call_ctors").?).?;
1862 const loc = wasm.findGlobalSymbol("__wasm_call_ctors").?;
18321863 const symbol = loc.getSymbol(wasm);
18331864 // create type (() -> nil) as we do not have any parameters or return value.
18341865 const ty_index = try wasm.putOrGetFuncType(.{ .params = &[_]std.wasm.Valtype{}, .returns = &[_]std.wasm.Valtype{} });
......@@ -2039,12 +2070,7 @@ fn setupExports(wasm: *Wasm) !void {
20392070 var failed_exports = false;
20402071
20412072 for (force_exp_names) |exp_name| {
2042 const name_index = wasm.string_table.getOffset(exp_name) orelse {
2043 log.err("could not export '{s}', symbol not found", .{exp_name});
2044 failed_exports = true;
2045 continue;
2046 };
2047 const loc = wasm.globals.get(name_index) orelse {
2073 const loc = wasm.findGlobalSymbol(exp_name) orelse {
20482074 log.err("could not export '{s}', symbol not found", .{exp_name});
20492075 failed_exports = true;
20502076 continue;
......@@ -2100,7 +2126,7 @@ fn setupExports(wasm: *Wasm) !void {
21002126fn setupStart(wasm: *Wasm) !void {
21012127 const entry_name = wasm.base.options.entry orelse "_start";
21022128
2103 const symbol_name_offset = wasm.string_table.getOffset(entry_name) orelse {
2129 const symbol_loc = wasm.findGlobalSymbol(entry_name) orelse {
21042130 if (wasm.base.options.output_mode == .Exe) {
21052131 if (wasm.base.options.wasi_exec_model == .reactor) return; // Not required for reactors
21062132 } else {
......@@ -2110,10 +2136,6 @@ fn setupStart(wasm: *Wasm) !void {
21102136 return error.MissingSymbol;
21112137 };
21122138
2113 const symbol_loc = wasm.globals.get(symbol_name_offset) orelse {
2114 log.err("Entry symbol '{s}' not found", .{entry_name});
2115 return error.MissingSymbol;
2116 };
21172139 const symbol = symbol_loc.getSymbol(wasm);
21182140 if (symbol.tag != .function) {
21192141 log.err("Entry symbol '{s}' is not a function", .{entry_name});
......@@ -2133,6 +2155,8 @@ fn setupMemory(wasm: *Wasm) !void {
21332155 // Use the user-provided stack size or else we use 1MB by default
21342156 const stack_size = wasm.base.options.stack_size_override orelse page_size * 16;
21352157 const stack_alignment = 16; // wasm's stack alignment as specified by tool-convention
2158 const heap_alignment = 16; // wasm's heap alignment as specified by tool-convention
2159
21362160 // Always place the stack at the start by default
21372161 // unless the user specified the global-base flag
21382162 var place_stack_first = true;
......@@ -2151,8 +2175,13 @@ fn setupMemory(wasm: *Wasm) !void {
21512175 }
21522176
21532177 var offset: u32 = @intCast(u32, memory_ptr);
2154 for (wasm.data_segments.values()) |segment_index| {
2155 const segment = &wasm.segments.items[segment_index];
2178 var data_seg_it = wasm.data_segments.iterator();
2179 while (data_seg_it.next()) |entry| {
2180 if (mem.eql(u8, entry.key_ptr.*, ".synthetic")) {
2181 // do not update synthetic segments as they are not part of the output
2182 continue;
2183 }
2184 const segment = &wasm.segments.items[entry.value_ptr.*];
21562185 memory_ptr = std.mem.alignForwardGeneric(u64, memory_ptr, segment.alignment);
21572186 memory_ptr += segment.size;
21582187 segment.offset = offset;
......@@ -2165,6 +2194,16 @@ fn setupMemory(wasm: *Wasm) !void {
21652194 wasm.wasm_globals.items[0].init.i32_const = @bitCast(i32, @intCast(u32, memory_ptr));
21662195 }
21672196
2197 // One of the linked object files has a reference to the __heap_base symbol.
2198 // We must set its virtual address so it can be used in relocations.
2199 if (wasm.findGlobalSymbol("__heap_base")) |loc| {
2200 const segment_index = wasm.data_segments.get(".synthetic").?;
2201 const segment = &wasm.segments.items[segment_index];
2202 segment.offset = 0; // for simplicity we store the entire VA into atom's offset.
2203 const atom = wasm.symbol_atom.get(loc).?;
2204 atom.offset = @intCast(u32, mem.alignForwardGeneric(u64, memory_ptr, heap_alignment));
2205 }
2206
21682207 // Setup the max amount of pages
21692208 // For now we only support wasm32 by setting the maximum allowed memory size 2^32-1
21702209 const max_memory_allowed: u64 = (1 << 32) - 1;
......@@ -2666,6 +2705,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
26662705 var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;
26672706 try wasm.validateFeatures(&enabled_features, &emit_features_count);
26682707 try wasm.resolveSymbolsInArchives();
2708 try wasm.resolveLazySymbols();
26692709 try wasm.checkUndefinedSymbols();
26702710
26712711 try wasm.setupInitFunctions();
......@@ -2749,6 +2789,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
27492789 var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;
27502790 try wasm.validateFeatures(&enabled_features, &emit_features_count);
27512791 try wasm.resolveSymbolsInArchives();
2792 try wasm.resolveLazySymbols();
27522793 try wasm.checkUndefinedSymbols();
27532794
27542795 // When we finish/error we reset the state of the linker
......@@ -2992,7 +3033,7 @@ fn writeToFile(
29923033 if (wasm.function_table.count() > 0) {
29933034 const header_offset = try reserveVecSectionHeader(&binary_bytes);
29943035
2995 const table_loc = wasm.globals.get(wasm.string_table.getOffset("__indirect_function_table").?).?;
3036 const table_loc = wasm.findGlobalSymbol("__indirect_function_table").?;
29963037 const table_sym = table_loc.getSymbol(wasm);
29973038
29983039 var flags: u32 = if (table_sym.index == 0) 0x0 else 0x02; // passive with implicit 0-index table or set table index manually
......@@ -3031,10 +3072,12 @@ fn writeToFile(
30313072 defer sorted_atoms.deinit();
30323073
30333074 while (true) {
3034 if (!is_obj) {
3035 atom.resolveRelocs(wasm);
3075 if (wasm.resolved_symbols.contains(atom.symbolLoc())) {
3076 if (!is_obj) {
3077 atom.resolveRelocs(wasm);
3078 }
3079 sorted_atoms.appendAssumeCapacity(atom);
30363080 }
3037 sorted_atoms.appendAssumeCapacity(atom);
30383081 atom = atom.next orelse break;
30393082 }
30403083
......@@ -3075,10 +3118,11 @@ fn writeToFile(
30753118 // do not output 'bss' section unless we import memory and therefore
30763119 // want to guarantee the data is zero initialized
30773120 if (!import_memory and std.mem.eql(u8, entry.key_ptr.*, ".bss")) continue;
3078 segment_count += 1;
30793121 const atom_index = entry.value_ptr.*;
3080 var atom: *Atom = wasm.atoms.getPtr(atom_index).?.*.getFirst();
30813122 const segment = wasm.segments.items[atom_index];
3123 if (segment.size == 0) continue; // do not emit empty segments
3124 segment_count += 1;
3125 var atom: *Atom = wasm.atoms.getPtr(atom_index).?.*.getFirst();
30823126
30833127 // flag and index to memory section (currently, there can only be 1 memory section in wasm)
30843128 try leb.writeULEB128(binary_writer, @as(u32, 0));
src/link/Wasm/types.zig+1
......@@ -129,6 +129,7 @@ pub const Segment = struct {
129129 /// file or binary. When `merge_segments` is true, this will return the
130130 /// short name. i.e. ".rodata". When false, it returns the entire name instead.
131131 pub fn outputName(self: Segment, merge_segments: bool) []const u8 {
132 if (std.mem.startsWith(u8, self.name, ".synthetic")) return ".synthetic"; // always merge
132133 if (!merge_segments) return self.name;
133134 if (std.mem.startsWith(u8, self.name, ".rodata.")) {
134135 return ".rodata";