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
signaturelock-open 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...@@ -475,7 +475,7 @@ fn createSyntheticSymbol(wasm: *Wasm, name: []const u8, tag: Symbol.Tag) !Symbol
475 .index = undefined,475 .index = undefined,
476 });476 });
477 try wasm.resolved_symbols.putNoClobber(wasm.base.allocator, loc, {});477 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);
479 return loc;479 return loc;
480}480}
481/// Initializes symbols and atoms for the debug sections481/// Initializes symbols and atoms for the debug sections
...@@ -851,6 +851,35 @@ fn validateFeatures(...@@ -851,6 +851,35 @@ fn validateFeatures(
851 to_emit.* = allowed;851 to_emit.* = allowed;
852}852}
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
854fn checkUndefinedSymbols(wasm: *const Wasm) !void {883fn checkUndefinedSymbols(wasm: *const Wasm) !void {
855 if (wasm.base.options.output_mode == .Obj) return;884 if (wasm.base.options.output_mode == .Obj) return;
856 if (wasm.base.options.import_symbols) return;885 if (wasm.base.options.import_symbols) return;
...@@ -1458,14 +1487,13 @@ fn mapFunctionTable(wasm: *Wasm) void {...@@ -1458,14 +1487,13 @@ fn mapFunctionTable(wasm: *Wasm) void {
1458 }1487 }
14591488
1460 if (wasm.base.options.import_table or wasm.base.options.output_mode == .Obj) {1489 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").?;
1462 const import = wasm.imports.getPtr(sym_loc).?;1491 const import = wasm.imports.getPtr(sym_loc).?;
1463 import.kind.table.limits.min = index - 1; // we start at index 1.1492 import.kind.table.limits.min = index - 1; // we start at index 1.
1464 } else if (index > 1) {1493 } else if (index > 1) {
1465 log.debug("Appending indirect function table", .{});1494 log.debug("Appending indirect function table", .{});
1466 const offset = wasm.string_table.getOffset("__indirect_function_table").?;1495 const sym_loc = wasm.findGlobalSymbol("__indirect_function_table").?;
1467 const sym_with_loc = wasm.globals.get(offset).?;1496 const symbol = sym_loc.getSymbol(wasm);
1468 const symbol = sym_with_loc.getSymbol(wasm);
1469 const table = &wasm.tables.items[symbol.index - wasm.imported_tables_count];1497 const table = &wasm.tables.items[symbol.index - wasm.imported_tables_count];
1470 table.limits = .{ .min = index, .max = index };1498 table.limits = .{ .min = index, .max = index };
1471 }1499 }
...@@ -1544,6 +1572,7 @@ const Kind = union(enum) {...@@ -1544,6 +1572,7 @@ const Kind = union(enum) {
1544 read_only,1572 read_only,
1545 uninitialized,1573 uninitialized,
1546 initialized,1574 initialized,
1575 synthetic,
1547 },1576 },
1548 function: FnData,1577 function: FnData,
15491578
...@@ -1554,6 +1583,7 @@ const Kind = union(enum) {...@@ -1554,6 +1583,7 @@ const Kind = union(enum) {
1554 .read_only => return ".rodata.",1583 .read_only => return ".rodata.",
1555 .uninitialized => return ".bss.",1584 .uninitialized => return ".bss.",
1556 .initialized => return ".data.",1585 .initialized => return ".data.",
1586 .synthetic => return ".synthetic",
1557 }1587 }
1558 }1588 }
1559};1589};
...@@ -1690,9 +1720,14 @@ fn allocateAtoms(wasm: *Wasm) !void {...@@ -1690,9 +1720,14 @@ fn allocateAtoms(wasm: *Wasm) !void {
1690 var offset: u32 = 0;1720 var offset: u32 = 0;
1691 while (true) {1721 while (true) {
1692 const symbol_loc = atom.symbolLoc();1722 const symbol_loc = atom.symbolLoc();
1693 if (!wasm.resolved_symbols.contains(symbol_loc)) {1723 if (wasm.code_section_index) |index| {
1694 atom = atom.next orelse break;1724 if (index == entry.key_ptr.*) {
1695 continue;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 }
1696 }1731 }
1697 offset = std.mem.alignForwardGeneric(u32, offset, atom.alignment);1732 offset = std.mem.alignForwardGeneric(u32, offset, atom.alignment);
1698 atom.offset = offset;1733 atom.offset = offset;
...@@ -1727,6 +1762,7 @@ fn sortDataSegments(wasm: *Wasm) !void {...@@ -1727,6 +1762,7 @@ fn sortDataSegments(wasm: *Wasm) !void {
1727 if (mem.startsWith(u8, name, ".rodata")) return 0;1762 if (mem.startsWith(u8, name, ".rodata")) return 0;
1728 if (mem.startsWith(u8, name, ".data")) return 1;1763 if (mem.startsWith(u8, name, ".data")) return 1;
1729 if (mem.startsWith(u8, name, ".text")) return 2;1764 if (mem.startsWith(u8, name, ".text")) return 2;
1765 if (mem.startsWith(u8, name, ".synthetic")) return 100; // always at end
1730 return 3;1766 return 3;
1731 }1767 }
1732 };1768 };
...@@ -1789,7 +1825,7 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void {...@@ -1789,7 +1825,7 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void {
1789 if (wasm.code_section_index == null) {1825 if (wasm.code_section_index == null) {
1790 // Make sure to remove it from the resolved symbols so we do not emit1826 // Make sure to remove it from the resolved symbols so we do not emit
1791 // it within any section. TODO: Remove this once we implement garbage collection.1827 // 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").?;
1793 std.debug.assert(wasm.resolved_symbols.swapRemove(loc));1829 std.debug.assert(wasm.resolved_symbols.swapRemove(loc));
1794 return;1830 return;
1795 }1831 }
...@@ -1806,11 +1842,6 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void {...@@ -1806,11 +1842,6 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void {
1806 // call constructors1842 // call constructors
1807 for (wasm.init_funcs.items) |init_func_loc| {1843 for (wasm.init_funcs.items) |init_func_loc| {
1808 const symbol = init_func_loc.getSymbol(wasm);1844 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)));
1814 const func = wasm.functions.values()[symbol.index - wasm.imported_functions_count];1845 const func = wasm.functions.values()[symbol.index - wasm.imported_functions_count];
1815 const ty = wasm.func_types.items[func.type_index];1846 const ty = wasm.func_types.items[func.type_index];
18161847
...@@ -1828,7 +1859,7 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void {...@@ -1828,7 +1859,7 @@ fn initializeCallCtorsFunction(wasm: *Wasm) !void {
1828 try writer.writeByte(std.wasm.opcode(.end));1859 try writer.writeByte(std.wasm.opcode(.end));
1829 }1860 }
18301861
1831 const loc = wasm.globals.get(wasm.string_table.getOffset("__wasm_call_ctors").?).?;1862 const loc = wasm.findGlobalSymbol("__wasm_call_ctors").?;
1832 const symbol = loc.getSymbol(wasm);1863 const symbol = loc.getSymbol(wasm);
1833 // create type (() -> nil) as we do not have any parameters or return value.1864 // create type (() -> nil) as we do not have any parameters or return value.
1834 const ty_index = try wasm.putOrGetFuncType(.{ .params = &[_]std.wasm.Valtype{}, .returns = &[_]std.wasm.Valtype{} });1865 const ty_index = try wasm.putOrGetFuncType(.{ .params = &[_]std.wasm.Valtype{}, .returns = &[_]std.wasm.Valtype{} });
...@@ -2039,12 +2070,7 @@ fn setupExports(wasm: *Wasm) !void {...@@ -2039,12 +2070,7 @@ fn setupExports(wasm: *Wasm) !void {
2039 var failed_exports = false;2070 var failed_exports = false;
20402071
2041 for (force_exp_names) |exp_name| {2072 for (force_exp_names) |exp_name| {
2042 const name_index = wasm.string_table.getOffset(exp_name) orelse {2073 const loc = wasm.findGlobalSymbol(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 {
2048 log.err("could not export '{s}', symbol not found", .{exp_name});2074 log.err("could not export '{s}', symbol not found", .{exp_name});
2049 failed_exports = true;2075 failed_exports = true;
2050 continue;2076 continue;
...@@ -2100,7 +2126,7 @@ fn setupExports(wasm: *Wasm) !void {...@@ -2100,7 +2126,7 @@ fn setupExports(wasm: *Wasm) !void {
2100fn setupStart(wasm: *Wasm) !void {2126fn setupStart(wasm: *Wasm) !void {
2101 const entry_name = wasm.base.options.entry orelse "_start";2127 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 {
2104 if (wasm.base.options.output_mode == .Exe) {2130 if (wasm.base.options.output_mode == .Exe) {
2105 if (wasm.base.options.wasi_exec_model == .reactor) return; // Not required for reactors2131 if (wasm.base.options.wasi_exec_model == .reactor) return; // Not required for reactors
2106 } else {2132 } else {
...@@ -2110,10 +2136,6 @@ fn setupStart(wasm: *Wasm) !void {...@@ -2110,10 +2136,6 @@ fn setupStart(wasm: *Wasm) !void {
2110 return error.MissingSymbol;2136 return error.MissingSymbol;
2111 };2137 };
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 };
2117 const symbol = symbol_loc.getSymbol(wasm);2139 const symbol = symbol_loc.getSymbol(wasm);
2118 if (symbol.tag != .function) {2140 if (symbol.tag != .function) {
2119 log.err("Entry symbol '{s}' is not a function", .{entry_name});2141 log.err("Entry symbol '{s}' is not a function", .{entry_name});
...@@ -2133,6 +2155,8 @@ fn setupMemory(wasm: *Wasm) !void {...@@ -2133,6 +2155,8 @@ fn setupMemory(wasm: *Wasm) !void {
2133 // Use the user-provided stack size or else we use 1MB by default2155 // Use the user-provided stack size or else we use 1MB by default
2134 const stack_size = wasm.base.options.stack_size_override orelse page_size * 16;2156 const stack_size = wasm.base.options.stack_size_override orelse page_size * 16;
2135 const stack_alignment = 16; // wasm's stack alignment as specified by tool-convention2157 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
2136 // Always place the stack at the start by default2160 // Always place the stack at the start by default
2137 // unless the user specified the global-base flag2161 // unless the user specified the global-base flag
2138 var place_stack_first = true;2162 var place_stack_first = true;
...@@ -2151,8 +2175,13 @@ fn setupMemory(wasm: *Wasm) !void {...@@ -2151,8 +2175,13 @@ fn setupMemory(wasm: *Wasm) !void {
2151 }2175 }
21522176
2153 var offset: u32 = @intCast(u32, memory_ptr);2177 var offset: u32 = @intCast(u32, memory_ptr);
2154 for (wasm.data_segments.values()) |segment_index| {2178 var data_seg_it = wasm.data_segments.iterator();
2155 const segment = &wasm.segments.items[segment_index];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.*];
2156 memory_ptr = std.mem.alignForwardGeneric(u64, memory_ptr, segment.alignment);2185 memory_ptr = std.mem.alignForwardGeneric(u64, memory_ptr, segment.alignment);
2157 memory_ptr += segment.size;2186 memory_ptr += segment.size;
2158 segment.offset = offset;2187 segment.offset = offset;
...@@ -2165,6 +2194,16 @@ fn setupMemory(wasm: *Wasm) !void {...@@ -2165,6 +2194,16 @@ fn setupMemory(wasm: *Wasm) !void {
2165 wasm.wasm_globals.items[0].init.i32_const = @bitCast(i32, @intCast(u32, memory_ptr));2194 wasm.wasm_globals.items[0].init.i32_const = @bitCast(i32, @intCast(u32, memory_ptr));
2166 }2195 }
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
2168 // Setup the max amount of pages2207 // Setup the max amount of pages
2169 // For now we only support wasm32 by setting the maximum allowed memory size 2^32-12208 // For now we only support wasm32 by setting the maximum allowed memory size 2^32-1
2170 const max_memory_allowed: u64 = (1 << 32) - 1;2209 const max_memory_allowed: u64 = (1 << 32) - 1;
...@@ -2666,6 +2705,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l...@@ -2666,6 +2705,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
2666 var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;2705 var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;
2667 try wasm.validateFeatures(&enabled_features, &emit_features_count);2706 try wasm.validateFeatures(&enabled_features, &emit_features_count);
2668 try wasm.resolveSymbolsInArchives();2707 try wasm.resolveSymbolsInArchives();
2708 try wasm.resolveLazySymbols();
2669 try wasm.checkUndefinedSymbols();2709 try wasm.checkUndefinedSymbols();
26702710
2671 try wasm.setupInitFunctions();2711 try wasm.setupInitFunctions();
...@@ -2749,6 +2789,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -2749,6 +2789,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
2749 var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;2789 var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;
2750 try wasm.validateFeatures(&enabled_features, &emit_features_count);2790 try wasm.validateFeatures(&enabled_features, &emit_features_count);
2751 try wasm.resolveSymbolsInArchives();2791 try wasm.resolveSymbolsInArchives();
2792 try wasm.resolveLazySymbols();
2752 try wasm.checkUndefinedSymbols();2793 try wasm.checkUndefinedSymbols();
27532794
2754 // When we finish/error we reset the state of the linker2795 // When we finish/error we reset the state of the linker
...@@ -2992,7 +3033,7 @@ fn writeToFile(...@@ -2992,7 +3033,7 @@ fn writeToFile(
2992 if (wasm.function_table.count() > 0) {3033 if (wasm.function_table.count() > 0) {
2993 const header_offset = try reserveVecSectionHeader(&binary_bytes);3034 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").?;
2996 const table_sym = table_loc.getSymbol(wasm);3037 const table_sym = table_loc.getSymbol(wasm);
29973038
2998 var flags: u32 = if (table_sym.index == 0) 0x0 else 0x02; // passive with implicit 0-index table or set table index manually3039 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(...@@ -3031,10 +3072,12 @@ fn writeToFile(
3031 defer sorted_atoms.deinit();3072 defer sorted_atoms.deinit();
30323073
3033 while (true) {3074 while (true) {
3034 if (!is_obj) {3075 if (wasm.resolved_symbols.contains(atom.symbolLoc())) {
3035 atom.resolveRelocs(wasm);3076 if (!is_obj) {
3077 atom.resolveRelocs(wasm);
3078 }
3079 sorted_atoms.appendAssumeCapacity(atom);
3036 }3080 }
3037 sorted_atoms.appendAssumeCapacity(atom);
3038 atom = atom.next orelse break;3081 atom = atom.next orelse break;
3039 }3082 }
30403083
...@@ -3075,10 +3118,11 @@ fn writeToFile(...@@ -3075,10 +3118,11 @@ fn writeToFile(
3075 // do not output 'bss' section unless we import memory and therefore3118 // do not output 'bss' section unless we import memory and therefore
3076 // want to guarantee the data is zero initialized3119 // want to guarantee the data is zero initialized
3077 if (!import_memory and std.mem.eql(u8, entry.key_ptr.*, ".bss")) continue;3120 if (!import_memory and std.mem.eql(u8, entry.key_ptr.*, ".bss")) continue;
3078 segment_count += 1;
3079 const atom_index = entry.value_ptr.*;3121 const atom_index = entry.value_ptr.*;
3080 var atom: *Atom = wasm.atoms.getPtr(atom_index).?.*.getFirst();
3081 const segment = wasm.segments.items[atom_index];3122 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
3083 // flag and index to memory section (currently, there can only be 1 memory section in wasm)3127 // flag and index to memory section (currently, there can only be 1 memory section in wasm)
3084 try leb.writeULEB128(binary_writer, @as(u32, 0));3128 try leb.writeULEB128(binary_writer, @as(u32, 0));
src/link/Wasm/types.zig+1
...@@ -129,6 +129,7 @@ pub const Segment = struct {...@@ -129,6 +129,7 @@ pub const Segment = struct {
129 /// file or binary. When `merge_segments` is true, this will return the129 /// file or binary. When `merge_segments` is true, this will return the
130 /// short name. i.e. ".rodata". When false, it returns the entire name instead.130 /// short name. i.e. ".rodata". When false, it returns the entire name instead.
131 pub fn outputName(self: Segment, merge_segments: bool) []const u8 {131 pub fn outputName(self: Segment, merge_segments: bool) []const u8 {
132 if (std.mem.startsWith(u8, self.name, ".synthetic")) return ".synthetic"; // always merge
132 if (!merge_segments) return self.name;133 if (!merge_segments) return self.name;
133 if (std.mem.startsWith(u8, self.name, ".rodata.")) {134 if (std.mem.startsWith(u8, self.name, ".rodata.")) {
134 return ".rodata";135 return ".rodata";