authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-01-14 17:58:09+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-01-14 17:58:09+01:00
log18191b80b6381eb41adb4354a243190865801212
treeaebc5cea62db5bf02ce273eb8ed0387f75e13045
parent6b3f59c3a735ddbda3b3a62a0dfb5d55fa045f57
parent5468684456b13b6465c4fcd50c072e5d5c8536a3
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #14302 from Luukdegram/wasm-ctor

wasm-linker: implement linking with WASI-libc

3 files changed, 309 insertions(+), 61 deletions(-)

src/link.zig+1
...@@ -716,6 +716,7 @@ pub const File = struct {...@@ -716,6 +716,7 @@ pub const File = struct {
716 InvalidFeatureSet,716 InvalidFeatureSet,
717 InvalidFormat,717 InvalidFormat,
718 InvalidIndex,718 InvalidIndex,
719 InvalidInitFunc,
719 InvalidMagicByte,720 InvalidMagicByte,
720 InvalidWasmVersion,721 InvalidWasmVersion,
721 LLDCrashed,722 LLDCrashed,
src/link/Wasm.zig+307-61
...@@ -118,6 +118,9 @@ memories: std.wasm.Memory = .{ .limits = .{ .min = 0, .max = null } },...@@ -118,6 +118,9 @@ memories: std.wasm.Memory = .{ .limits = .{ .min = 0, .max = null } },
118tables: std.ArrayListUnmanaged(std.wasm.Table) = .{},118tables: std.ArrayListUnmanaged(std.wasm.Table) = .{},
119/// Output export section119/// Output export section
120exports: std.ArrayListUnmanaged(types.Export) = .{},120exports: std.ArrayListUnmanaged(types.Export) = .{},
121/// List of initialization functions. These must be called in order of priority
122/// by the (synthetic) __wasm_call_ctors function.
123init_funcs: std.ArrayListUnmanaged(InitFuncLoc) = .{},
121124
122/// Indirect function table, used to call function pointers125/// Indirect function table, used to call function pointers
123/// When this is non-zero, we must emit a table entry,126/// When this is non-zero, we must emit a table entry,
...@@ -238,6 +241,34 @@ pub const SymbolLoc = struct {...@@ -238,6 +241,34 @@ pub const SymbolLoc = struct {
238 }241 }
239};242};
240243
244// Contains the location of the function symbol, as well as
245/// the priority itself of the initialization function.
246pub const InitFuncLoc = struct {
247 /// object file index in the list of objects.
248 /// Unlike `SymbolLoc` this cannot be `null` as we never define
249 /// our own ctors.
250 file: u16,
251 /// Symbol index within the corresponding object file.
252 index: u32,
253 /// The priority in which the constructor must be called.
254 priority: u32,
255
256 /// From a given `InitFuncLoc` returns the corresponding function symbol
257 fn getSymbol(loc: InitFuncLoc, wasm: *const Wasm) *Symbol {
258 return getSymbolLoc(loc).getSymbol(wasm);
259 }
260
261 /// Turns the given `InitFuncLoc` into a `SymbolLoc`
262 fn getSymbolLoc(loc: InitFuncLoc) SymbolLoc {
263 return .{ .file = loc.file, .index = loc.index };
264 }
265
266 /// Returns true when `lhs` has a higher priority (e.i. value closer to 0) than `rhs`.
267 fn lessThan(ctx: void, lhs: InitFuncLoc, rhs: InitFuncLoc) bool {
268 _ = ctx;
269 return lhs.priority < rhs.priority;
270 }
271};
241/// Generic string table that duplicates strings272/// Generic string table that duplicates strings
242/// and converts them into offsets instead.273/// and converts them into offsets instead.
243pub const StringTable = struct {274pub const StringTable = struct {
...@@ -393,6 +424,16 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option...@@ -393,6 +424,16 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
393 }424 }
394 }425 }
395426
427 // create __wasm_call_ctors
428 {
429 const loc = try wasm_bin.createSyntheticSymbol("__wasm_call_ctors", .function);
430 const symbol = loc.getSymbol(wasm_bin);
431 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
432 // we do not know the function index until after we merged all sections.
433 // Therefore we set `symbol.index` and create its corresponding references
434 // at the end during `initializeCallCtorsFunction`.
435 }
436
396 if (!options.strip and options.module != null) {437 if (!options.strip and options.module != null) {
397 wasm_bin.dwarf = Dwarf.init(allocator, &wasm_bin.base, options.target);438 wasm_bin.dwarf = Dwarf.init(allocator, &wasm_bin.base, options.target);
398 try wasm_bin.initDebugSections();439 try wasm_bin.initDebugSections();
...@@ -434,7 +475,7 @@ fn createSyntheticSymbol(wasm: *Wasm, name: []const u8, tag: Symbol.Tag) !Symbol...@@ -434,7 +475,7 @@ fn createSyntheticSymbol(wasm: *Wasm, name: []const u8, tag: Symbol.Tag) !Symbol
434 .index = undefined,475 .index = undefined,
435 });476 });
436 try wasm.resolved_symbols.putNoClobber(wasm.base.allocator, loc, {});477 try wasm.resolved_symbols.putNoClobber(wasm.base.allocator, loc, {});
437 try wasm.globals.putNoClobber(wasm.base.allocator, name_offset, loc);478 try wasm.globals.put(wasm.base.allocator, name_offset, loc);
438 return loc;479 return loc;
439}480}
440/// Initializes symbols and atoms for the debug sections481/// Initializes symbols and atoms for the debug sections
...@@ -600,27 +641,34 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_index: u16) !void {...@@ -600,27 +641,34 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_index: u16) !void {
600 }641 }
601642
602 if (existing_sym.isUndefined() and symbol.isUndefined()) {643 if (existing_sym.isUndefined() and symbol.isUndefined()) {
603 const existing_name = if (existing_loc.file) |file_index| blk: {644 // only verify module/import name for function symbols
604 const obj = wasm.objects.items[file_index];645 if (symbol.tag == .function) {
605 const name_index = obj.findImport(symbol.tag.externalType(), existing_sym.index).module_name;646 const existing_name = if (existing_loc.file) |file_index| blk: {
606 break :blk obj.string_table.get(name_index);647 const obj = wasm.objects.items[file_index];
607 } else blk: {648 const name_index = obj.findImport(symbol.tag.externalType(), existing_sym.index).module_name;
608 const name_index = wasm.imports.get(existing_loc).?.module_name;649 break :blk obj.string_table.get(name_index);
609 break :blk wasm.string_table.get(name_index);650 } else blk: {
610 };651 const name_index = wasm.imports.get(existing_loc).?.module_name;
652 break :blk wasm.string_table.get(name_index);
653 };
611654
612 const module_index = object.findImport(symbol.tag.externalType(), symbol.index).module_name;655 const module_index = object.findImport(symbol.tag.externalType(), symbol.index).module_name;
613 const module_name = object.string_table.get(module_index);656 const module_name = object.string_table.get(module_index);
614 if (!mem.eql(u8, existing_name, module_name)) {657 if (!mem.eql(u8, existing_name, module_name)) {
615 log.err("symbol '{s}' module name mismatch. Expected '{s}', but found '{s}'", .{658 log.err("symbol '{s}' module name mismatch. Expected '{s}', but found '{s}'", .{
616 sym_name,659 sym_name,
617 existing_name,660 existing_name,
618 module_name,661 module_name,
619 });662 });
620 log.err(" first definition in '{s}'", .{existing_file_path});663 log.err(" first definition in '{s}'", .{existing_file_path});
621 log.err(" next definition in '{s}'", .{object.name});664 log.err(" next definition in '{s}'", .{object.name});
622 return error.ModuleNameMismatch;665 return error.ModuleNameMismatch;
666 }
623 }667 }
668
669 // both undefined so skip overwriting existing symbol and discard the new symbol
670 try wasm.discarded.put(wasm.base.allocator, location, existing_loc);
671 continue;
624 }672 }
625673
626 if (existing_sym.tag == .global) {674 if (existing_sym.tag == .global) {
...@@ -646,8 +694,10 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_index: u16) !void {...@@ -646,8 +694,10 @@ fn resolveSymbolsInObject(wasm: *Wasm, object_index: u16) !void {
646 }694 }
647 }695 }
648696
649 // when both symbols are weak, we skip overwriting697 // when both symbols are weak, we skip overwriting unless the existing
650 if (existing_sym.isWeak() and symbol.isWeak()) {698 // symbol is weak and the new one isn't, in which case we *do* overwrite it.
699 if (existing_sym.isWeak() and symbol.isWeak()) blk: {
700 if (existing_sym.isUndefined() and !symbol.isUndefined()) break :blk;
651 try wasm.discarded.put(wasm.base.allocator, location, existing_loc);701 try wasm.discarded.put(wasm.base.allocator, location, existing_loc);
652 continue;702 continue;
653 }703 }
...@@ -801,6 +851,51 @@ fn validateFeatures(...@@ -801,6 +851,51 @@ fn validateFeatures(
801 to_emit.* = allowed;851 to_emit.* = allowed;
802}852}
803853
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 if (wasm.undefs.fetchSwapRemove("__heap_end")) |kv| {
876 const loc = try wasm.createSyntheticSymbol("__heap_end", .data);
877 try wasm.discarded.putNoClobber(wasm.base.allocator, kv.value, loc);
878 _ = wasm.resolved_symbols.swapRemove(loc);
879
880 const atom = try wasm.base.allocator.create(Atom);
881 errdefer wasm.base.allocator.destroy(atom);
882 try wasm.managed_atoms.append(wasm.base.allocator, atom);
883 atom.* = Atom.empty;
884 atom.sym_index = loc.index;
885 atom.alignment = 1;
886
887 try wasm.parseAtom(atom, .{ .data = .synthetic });
888 try wasm.symbol_atom.putNoClobber(wasm.base.allocator, loc, atom);
889 }
890}
891
892// Tries to find a global symbol by its name. Returns null when not found,
893/// and its location when it is found.
894fn findGlobalSymbol(wasm: *Wasm, name: []const u8) ?SymbolLoc {
895 const offset = wasm.string_table.getOffset(name) orelse return null;
896 return wasm.globals.get(offset);
897}
898
804fn checkUndefinedSymbols(wasm: *const Wasm) !void {899fn checkUndefinedSymbols(wasm: *const Wasm) !void {
805 if (wasm.base.options.output_mode == .Obj) return;900 if (wasm.base.options.output_mode == .Obj) return;
806 if (wasm.base.options.import_symbols) return;901 if (wasm.base.options.import_symbols) return;
...@@ -813,12 +908,8 @@ fn checkUndefinedSymbols(wasm: *const Wasm) !void {...@@ -813,12 +908,8 @@ fn checkUndefinedSymbols(wasm: *const Wasm) !void {
813 const file_name = if (undef.file) |file_index| name: {908 const file_name = if (undef.file) |file_index| name: {
814 break :name wasm.objects.items[file_index].name;909 break :name wasm.objects.items[file_index].name;
815 } else wasm.name;910 } else wasm.name;
816 const import_name = if (undef.file) |file_index| name: {911 const symbol_name = undef.getName(wasm);
817 const obj = wasm.objects.items[file_index];912 log.err("could not resolve undefined symbol '{s}'", .{symbol_name});
818 const name_index = obj.findImport(symbol.tag.externalType(), symbol.index).name;
819 break :name obj.string_table.get(name_index);
820 } else wasm.string_table.get(wasm.imports.get(undef).?.name);
821 log.err("could not resolve undefined symbol '{s}'", .{import_name});
822 log.err(" defined in '{s}'", .{file_name});913 log.err(" defined in '{s}'", .{file_name});
823 }914 }
824 }915 }
...@@ -885,6 +976,7 @@ pub fn deinit(wasm: *Wasm) void {...@@ -885,6 +976,7 @@ pub fn deinit(wasm: *Wasm) void {
885 wasm.wasm_globals.deinit(gpa);976 wasm.wasm_globals.deinit(gpa);
886 wasm.function_table.deinit(gpa);977 wasm.function_table.deinit(gpa);
887 wasm.tables.deinit(gpa);978 wasm.tables.deinit(gpa);
979 wasm.init_funcs.deinit(gpa);
888 wasm.exports.deinit(gpa);980 wasm.exports.deinit(gpa);
889981
890 wasm.string_table.deinit(gpa);982 wasm.string_table.deinit(gpa);
...@@ -1405,14 +1497,13 @@ fn mapFunctionTable(wasm: *Wasm) void {...@@ -1405,14 +1497,13 @@ fn mapFunctionTable(wasm: *Wasm) void {
1405 }1497 }
14061498
1407 if (wasm.base.options.import_table or wasm.base.options.output_mode == .Obj) {1499 if (wasm.base.options.import_table or wasm.base.options.output_mode == .Obj) {
1408 const sym_loc = wasm.globals.get(wasm.string_table.getOffset("__indirect_function_table").?).?;1500 const sym_loc = wasm.findGlobalSymbol("__indirect_function_table").?;
1409 const import = wasm.imports.getPtr(sym_loc).?;1501 const import = wasm.imports.getPtr(sym_loc).?;
1410 import.kind.table.limits.min = index - 1; // we start at index 1.1502 import.kind.table.limits.min = index - 1; // we start at index 1.
1411 } else if (index > 1) {1503 } else if (index > 1) {
1412 log.debug("Appending indirect function table", .{});1504 log.debug("Appending indirect function table", .{});
1413 const offset = wasm.string_table.getOffset("__indirect_function_table").?;1505 const sym_loc = wasm.findGlobalSymbol("__indirect_function_table").?;
1414 const sym_with_loc = wasm.globals.get(offset).?;1506 const symbol = sym_loc.getSymbol(wasm);
1415 const symbol = sym_with_loc.getSymbol(wasm);
1416 const table = &wasm.tables.items[symbol.index - wasm.imported_tables_count];1507 const table = &wasm.tables.items[symbol.index - wasm.imported_tables_count];
1417 table.limits = .{ .min = index, .max = index };1508 table.limits = .{ .min = index, .max = index };
1418 }1509 }
...@@ -1491,6 +1582,7 @@ const Kind = union(enum) {...@@ -1491,6 +1582,7 @@ const Kind = union(enum) {
1491 read_only,1582 read_only,
1492 uninitialized,1583 uninitialized,
1493 initialized,1584 initialized,
1585 synthetic,
1494 },1586 },
1495 function: FnData,1587 function: FnData,
14961588
...@@ -1501,6 +1593,7 @@ const Kind = union(enum) {...@@ -1501,6 +1593,7 @@ const Kind = union(enum) {
1501 .read_only => return ".rodata.",1593 .read_only => return ".rodata.",
1502 .uninitialized => return ".bss.",1594 .uninitialized => return ".bss.",
1503 .initialized => return ".data.",1595 .initialized => return ".data.",
1596 .synthetic => return ".synthetic",
1504 }1597 }
1505 }1598 }
1506};1599};
...@@ -1637,9 +1730,14 @@ fn allocateAtoms(wasm: *Wasm) !void {...@@ -1637,9 +1730,14 @@ fn allocateAtoms(wasm: *Wasm) !void {
1637 var offset: u32 = 0;1730 var offset: u32 = 0;
1638 while (true) {1731 while (true) {
1639 const symbol_loc = atom.symbolLoc();1732 const symbol_loc = atom.symbolLoc();
1640 if (!wasm.resolved_symbols.contains(symbol_loc)) {1733 if (wasm.code_section_index) |index| {
1641 atom = atom.next orelse break;1734 if (index == entry.key_ptr.*) {
1642 continue;1735 if (!wasm.resolved_symbols.contains(symbol_loc)) {
1736 // only allocate resolved function body's.
1737 atom = atom.next orelse break;
1738 continue;
1739 }
1740 }
1643 }1741 }
1644 offset = std.mem.alignForwardGeneric(u32, offset, atom.alignment);1742 offset = std.mem.alignForwardGeneric(u32, offset, atom.alignment);
1645 atom.offset = offset;1743 atom.offset = offset;
...@@ -1674,6 +1772,7 @@ fn sortDataSegments(wasm: *Wasm) !void {...@@ -1674,6 +1772,7 @@ fn sortDataSegments(wasm: *Wasm) !void {
1674 if (mem.startsWith(u8, name, ".rodata")) return 0;1772 if (mem.startsWith(u8, name, ".rodata")) return 0;
1675 if (mem.startsWith(u8, name, ".data")) return 1;1773 if (mem.startsWith(u8, name, ".data")) return 1;
1676 if (mem.startsWith(u8, name, ".text")) return 2;1774 if (mem.startsWith(u8, name, ".text")) return 2;
1775 if (mem.startsWith(u8, name, ".synthetic")) return 100; // always at end
1677 return 3;1776 return 3;
1678 }1777 }
1679 };1778 };
...@@ -1687,6 +1786,125 @@ fn sortDataSegments(wasm: *Wasm) !void {...@@ -1687,6 +1786,125 @@ fn sortDataSegments(wasm: *Wasm) !void {
1687 wasm.data_segments = new_mapping;1786 wasm.data_segments = new_mapping;
1688}1787}
16891788
1789/// Obtains all initfuncs from each object file, verifies its function signature,
1790/// and then appends it to our final `init_funcs` list.
1791/// After all functions have been inserted, the functions will be ordered based
1792/// on their priority.
1793/// NOTE: This function must be called before we merged any other section.
1794/// This is because all init funcs in the object files contain references to the
1795/// original functions and their types. We need to know the type to verify it doesn't
1796/// contain any parameters.
1797fn setupInitFunctions(wasm: *Wasm) !void {
1798 for (wasm.objects.items) |object, file_index| {
1799 try wasm.init_funcs.ensureUnusedCapacity(wasm.base.allocator, object.init_funcs.len);
1800 for (object.init_funcs) |init_func| {
1801 const symbol = object.symtable[init_func.symbol_index];
1802 const ty: std.wasm.Type = if (symbol.isUndefined()) ty: {
1803 const imp: types.Import = object.findImport(.function, symbol.index);
1804 break :ty object.func_types[imp.kind.function];
1805 } else ty: {
1806 const func_index = symbol.index - object.importedCountByKind(.function);
1807 const func = object.functions[func_index];
1808 break :ty object.func_types[func.type_index];
1809 };
1810 if (ty.params.len != 0) {
1811 log.err("constructor functions cannot take arguments: '{s}'", .{object.string_table.get(symbol.name)});
1812 return error.InvalidInitFunc;
1813 }
1814 log.debug("appended init func '{s}'\n", .{object.string_table.get(symbol.name)});
1815 wasm.init_funcs.appendAssumeCapacity(.{
1816 .index = init_func.symbol_index,
1817 .file = @intCast(u16, file_index),
1818 .priority = init_func.priority,
1819 });
1820 }
1821 }
1822
1823 // sort the initfunctions based on their priority
1824 std.sort.sort(InitFuncLoc, wasm.init_funcs.items, {}, InitFuncLoc.lessThan);
1825}
1826
1827/// Creates a function body for the `__wasm_call_ctors` symbol.
1828/// Loops over all constructors found in `init_funcs` and calls them
1829/// respectively based on their priority which was sorted by `setupInitFunctions`.
1830/// NOTE: This function must be called after we merged all sections to ensure the
1831/// references to the function stored in the symbol have been finalized so we end
1832/// up calling the resolved function.
1833fn initializeCallCtorsFunction(wasm: *Wasm) !void {
1834 // No code to emit, so also no ctors to call
1835 if (wasm.code_section_index == null) {
1836 // Make sure to remove it from the resolved symbols so we do not emit
1837 // it within any section. TODO: Remove this once we implement garbage collection.
1838 const loc = wasm.findGlobalSymbol("__wasm_call_ctors").?;
1839 std.debug.assert(wasm.resolved_symbols.swapRemove(loc));
1840 return;
1841 }
1842
1843 var function_body = std.ArrayList(u8).init(wasm.base.allocator);
1844 defer function_body.deinit();
1845 const writer = function_body.writer();
1846
1847 // Create the function body
1848 {
1849 // Write locals count (we have none)
1850 try leb.writeULEB128(writer, @as(u32, 0));
1851
1852 // call constructors
1853 for (wasm.init_funcs.items) |init_func_loc| {
1854 const symbol = init_func_loc.getSymbol(wasm);
1855 const func = wasm.functions.values()[symbol.index - wasm.imported_functions_count];
1856 const ty = wasm.func_types.items[func.type_index];
1857
1858 // Call function by its function index
1859 try writer.writeByte(std.wasm.opcode(.call));
1860 try leb.writeULEB128(writer, symbol.index);
1861
1862 // drop all returned values from the stack as __wasm_call_ctors has no return value
1863 for (ty.returns) |_| {
1864 try writer.writeByte(std.wasm.opcode(.drop));
1865 }
1866 }
1867
1868 // End function body
1869 try writer.writeByte(std.wasm.opcode(.end));
1870 }
1871
1872 const loc = wasm.findGlobalSymbol("__wasm_call_ctors").?;
1873 const symbol = loc.getSymbol(wasm);
1874 // create type (() -> nil) as we do not have any parameters or return value.
1875 const ty_index = try wasm.putOrGetFuncType(.{ .params = &[_]std.wasm.Valtype{}, .returns = &[_]std.wasm.Valtype{} });
1876 // create function with above type
1877 const func_index = wasm.imported_functions_count + @intCast(u32, wasm.functions.count());
1878 try wasm.functions.putNoClobber(
1879 wasm.base.allocator,
1880 .{ .file = null, .index = func_index },
1881 .{ .type_index = ty_index },
1882 );
1883 symbol.index = func_index;
1884
1885 // create the atom that will be output into the final binary
1886 const atom = try wasm.base.allocator.create(Atom);
1887 errdefer wasm.base.allocator.destroy(atom);
1888 atom.* = .{
1889 .size = @intCast(u32, function_body.items.len),
1890 .offset = 0,
1891 .sym_index = loc.index,
1892 .file = null,
1893 .alignment = 1,
1894 .next = null,
1895 .prev = null,
1896 .code = function_body.moveToUnmanaged(),
1897 .dbg_info_atom = undefined,
1898 };
1899 try wasm.managed_atoms.append(wasm.base.allocator, atom);
1900 try wasm.appendAtomAtIndex(wasm.code_section_index.?, atom);
1901 try wasm.symbol_atom.putNoClobber(wasm.base.allocator, loc, atom);
1902
1903 // `allocateAtoms` has already been called, set the atom's offset manually.
1904 // This is fine to do manually as we insert the atom at the very end.
1905 atom.offset = atom.prev.?.offset + atom.prev.?.size;
1906}
1907
1690fn setupImports(wasm: *Wasm) !void {1908fn setupImports(wasm: *Wasm) !void {
1691 log.debug("Merging imports", .{});1909 log.debug("Merging imports", .{});
1692 var discarded_it = wasm.discarded.keyIterator();1910 var discarded_it = wasm.discarded.keyIterator();
...@@ -1859,16 +2077,12 @@ fn setupExports(wasm: *Wasm) !void {...@@ -1859,16 +2077,12 @@ fn setupExports(wasm: *Wasm) !void {
18592077
1860 const force_exp_names = wasm.base.options.export_symbol_names;2078 const force_exp_names = wasm.base.options.export_symbol_names;
1861 if (force_exp_names.len > 0) {2079 if (force_exp_names.len > 0) {
1862 var failed_exports = try std.ArrayList([]const u8).initCapacity(wasm.base.allocator, force_exp_names.len);2080 var failed_exports = false;
1863 defer failed_exports.deinit();
18642081
1865 for (force_exp_names) |exp_name| {2082 for (force_exp_names) |exp_name| {
1866 const name_index = wasm.string_table.getOffset(exp_name) orelse {2083 const loc = wasm.findGlobalSymbol(exp_name) orelse {
1867 failed_exports.appendAssumeCapacity(exp_name);2084 log.err("could not export '{s}', symbol not found", .{exp_name});
1868 continue;2085 failed_exports = true;
1869 };
1870 const loc = wasm.globals.get(name_index) orelse {
1871 failed_exports.appendAssumeCapacity(exp_name);
1872 continue;2086 continue;
1873 };2087 };
18742088
...@@ -1876,10 +2090,7 @@ fn setupExports(wasm: *Wasm) !void {...@@ -1876,10 +2090,7 @@ fn setupExports(wasm: *Wasm) !void {
1876 symbol.setFlag(.WASM_SYM_EXPORTED);2090 symbol.setFlag(.WASM_SYM_EXPORTED);
1877 }2091 }
18782092
1879 if (failed_exports.items.len > 0) {2093 if (failed_exports) {
1880 for (failed_exports.items) |exp_name| {
1881 log.err("could not export '{s}', symbol not found", .{exp_name});
1882 }
1883 return error.MissingSymbol;2094 return error.MissingSymbol;
1884 }2095 }
1885 }2096 }
...@@ -1925,7 +2136,7 @@ fn setupExports(wasm: *Wasm) !void {...@@ -1925,7 +2136,7 @@ fn setupExports(wasm: *Wasm) !void {
1925fn setupStart(wasm: *Wasm) !void {2136fn setupStart(wasm: *Wasm) !void {
1926 const entry_name = wasm.base.options.entry orelse "_start";2137 const entry_name = wasm.base.options.entry orelse "_start";
19272138
1928 const symbol_name_offset = wasm.string_table.getOffset(entry_name) orelse {2139 const symbol_loc = wasm.findGlobalSymbol(entry_name) orelse {
1929 if (wasm.base.options.output_mode == .Exe) {2140 if (wasm.base.options.output_mode == .Exe) {
1930 if (wasm.base.options.wasi_exec_model == .reactor) return; // Not required for reactors2141 if (wasm.base.options.wasi_exec_model == .reactor) return; // Not required for reactors
1931 } else {2142 } else {
...@@ -1935,7 +2146,6 @@ fn setupStart(wasm: *Wasm) !void {...@@ -1935,7 +2146,6 @@ fn setupStart(wasm: *Wasm) !void {
1935 return error.MissingSymbol;2146 return error.MissingSymbol;
1936 };2147 };
19372148
1938 const symbol_loc = wasm.globals.get(symbol_name_offset).?;
1939 const symbol = symbol_loc.getSymbol(wasm);2149 const symbol = symbol_loc.getSymbol(wasm);
1940 if (symbol.tag != .function) {2150 if (symbol.tag != .function) {
1941 log.err("Entry symbol '{s}' is not a function", .{entry_name});2151 log.err("Entry symbol '{s}' is not a function", .{entry_name});
...@@ -1955,6 +2165,8 @@ fn setupMemory(wasm: *Wasm) !void {...@@ -1955,6 +2165,8 @@ fn setupMemory(wasm: *Wasm) !void {
1955 // Use the user-provided stack size or else we use 1MB by default2165 // Use the user-provided stack size or else we use 1MB by default
1956 const stack_size = wasm.base.options.stack_size_override orelse page_size * 16;2166 const stack_size = wasm.base.options.stack_size_override orelse page_size * 16;
1957 const stack_alignment = 16; // wasm's stack alignment as specified by tool-convention2167 const stack_alignment = 16; // wasm's stack alignment as specified by tool-convention
2168 const heap_alignment = 16; // wasm's heap alignment as specified by tool-convention
2169
1958 // Always place the stack at the start by default2170 // Always place the stack at the start by default
1959 // unless the user specified the global-base flag2171 // unless the user specified the global-base flag
1960 var place_stack_first = true;2172 var place_stack_first = true;
...@@ -1973,8 +2185,13 @@ fn setupMemory(wasm: *Wasm) !void {...@@ -1973,8 +2185,13 @@ fn setupMemory(wasm: *Wasm) !void {
1973 }2185 }
19742186
1975 var offset: u32 = @intCast(u32, memory_ptr);2187 var offset: u32 = @intCast(u32, memory_ptr);
1976 for (wasm.data_segments.values()) |segment_index| {2188 var data_seg_it = wasm.data_segments.iterator();
1977 const segment = &wasm.segments.items[segment_index];2189 while (data_seg_it.next()) |entry| {
2190 if (mem.eql(u8, entry.key_ptr.*, ".synthetic")) {
2191 // do not update synthetic segments as they are not part of the output
2192 continue;
2193 }
2194 const segment = &wasm.segments.items[entry.value_ptr.*];
1978 memory_ptr = std.mem.alignForwardGeneric(u64, memory_ptr, segment.alignment);2195 memory_ptr = std.mem.alignForwardGeneric(u64, memory_ptr, segment.alignment);
1979 memory_ptr += segment.size;2196 memory_ptr += segment.size;
1980 segment.offset = offset;2197 segment.offset = offset;
...@@ -1987,6 +2204,16 @@ fn setupMemory(wasm: *Wasm) !void {...@@ -1987,6 +2204,16 @@ fn setupMemory(wasm: *Wasm) !void {
1987 wasm.wasm_globals.items[0].init.i32_const = @bitCast(i32, @intCast(u32, memory_ptr));2204 wasm.wasm_globals.items[0].init.i32_const = @bitCast(i32, @intCast(u32, memory_ptr));
1988 }2205 }
19892206
2207 // One of the linked object files has a reference to the __heap_base symbol.
2208 // We must set its virtual address so it can be used in relocations.
2209 if (wasm.findGlobalSymbol("__heap_base")) |loc| {
2210 const segment_index = wasm.data_segments.get(".synthetic").?;
2211 const segment = &wasm.segments.items[segment_index];
2212 segment.offset = 0; // for simplicity we store the entire VA into atom's offset.
2213 const atom = wasm.symbol_atom.get(loc).?;
2214 atom.offset = @intCast(u32, mem.alignForwardGeneric(u64, memory_ptr, heap_alignment));
2215 }
2216
1990 // Setup the max amount of pages2217 // Setup the max amount of pages
1991 // For now we only support wasm32 by setting the maximum allowed memory size 2^32-12218 // For now we only support wasm32 by setting the maximum allowed memory size 2^32-1
1992 const max_memory_allowed: u64 = (1 << 32) - 1;2219 const max_memory_allowed: u64 = (1 << 32) - 1;
...@@ -2006,12 +2233,20 @@ fn setupMemory(wasm: *Wasm) !void {...@@ -2006,12 +2233,20 @@ fn setupMemory(wasm: *Wasm) !void {
2006 }2233 }
2007 memory_ptr = initial_memory;2234 memory_ptr = initial_memory;
2008 }2235 }
20092236 memory_ptr = mem.alignForwardGeneric(u64, memory_ptr, std.wasm.page_size);
2010 // In case we do not import memory, but define it ourselves,2237 // In case we do not import memory, but define it ourselves,
2011 // set the minimum amount of pages on the memory section.2238 // set the minimum amount of pages on the memory section.
2012 wasm.memories.limits.min = @intCast(u32, std.mem.alignForwardGeneric(u64, memory_ptr, page_size) / page_size);2239 wasm.memories.limits.min = @intCast(u32, memory_ptr / page_size);
2013 log.debug("Total memory pages: {d}", .{wasm.memories.limits.min});2240 log.debug("Total memory pages: {d}", .{wasm.memories.limits.min});
20142241
2242 if (wasm.findGlobalSymbol("__heap_end")) |loc| {
2243 const segment_index = wasm.data_segments.get(".synthetic").?;
2244 const segment = &wasm.segments.items[segment_index];
2245 segment.offset = 0;
2246 const atom = wasm.symbol_atom.get(loc).?;
2247 atom.offset = @intCast(u32, memory_ptr);
2248 }
2249
2015 if (wasm.base.options.max_memory) |max_memory| {2250 if (wasm.base.options.max_memory) |max_memory| {
2016 if (!std.mem.isAlignedGeneric(u64, max_memory, page_size)) {2251 if (!std.mem.isAlignedGeneric(u64, max_memory, page_size)) {
2017 log.err("Maximum memory must be {d}-byte aligned", .{page_size});2252 log.err("Maximum memory must be {d}-byte aligned", .{page_size});
...@@ -2488,8 +2723,10 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l...@@ -2488,8 +2723,10 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
2488 var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;2723 var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;
2489 try wasm.validateFeatures(&enabled_features, &emit_features_count);2724 try wasm.validateFeatures(&enabled_features, &emit_features_count);
2490 try wasm.resolveSymbolsInArchives();2725 try wasm.resolveSymbolsInArchives();
2726 try wasm.resolveLazySymbols();
2491 try wasm.checkUndefinedSymbols();2727 try wasm.checkUndefinedSymbols();
24922728
2729 try wasm.setupInitFunctions();
2493 try wasm.setupStart();2730 try wasm.setupStart();
2494 try wasm.setupImports();2731 try wasm.setupImports();
24952732
...@@ -2502,6 +2739,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l...@@ -2502,6 +2739,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
2502 wasm.mapFunctionTable();2739 wasm.mapFunctionTable();
2503 try wasm.mergeSections();2740 try wasm.mergeSections();
2504 try wasm.mergeTypes();2741 try wasm.mergeTypes();
2742 try wasm.initializeCallCtorsFunction();
2505 try wasm.setupExports();2743 try wasm.setupExports();
2506 try wasm.writeToFile(enabled_features, emit_features_count, arena);2744 try wasm.writeToFile(enabled_features, emit_features_count, arena);
25072745
...@@ -2569,11 +2807,13 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -2569,11 +2807,13 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
2569 var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;2807 var enabled_features: [@typeInfo(types.Feature.Tag).Enum.fields.len]bool = undefined;
2570 try wasm.validateFeatures(&enabled_features, &emit_features_count);2808 try wasm.validateFeatures(&enabled_features, &emit_features_count);
2571 try wasm.resolveSymbolsInArchives();2809 try wasm.resolveSymbolsInArchives();
2810 try wasm.resolveLazySymbols();
2572 try wasm.checkUndefinedSymbols();2811 try wasm.checkUndefinedSymbols();
25732812
2574 // When we finish/error we reset the state of the linker2813 // When we finish/error we reset the state of the linker
2575 // So we can rebuild the binary file on each incremental update2814 // So we can rebuild the binary file on each incremental update
2576 defer wasm.resetState();2815 defer wasm.resetState();
2816 try wasm.setupInitFunctions();
2577 try wasm.setupStart();2817 try wasm.setupStart();
2578 try wasm.setupImports();2818 try wasm.setupImports();
2579 if (wasm.base.options.module) |mod| {2819 if (wasm.base.options.module) |mod| {
...@@ -2616,6 +2856,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -2616,6 +2856,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
2616 wasm.mapFunctionTable();2856 wasm.mapFunctionTable();
2617 try wasm.mergeSections();2857 try wasm.mergeSections();
2618 try wasm.mergeTypes();2858 try wasm.mergeTypes();
2859 try wasm.initializeCallCtorsFunction();
2619 try wasm.setupExports();2860 try wasm.setupExports();
2620 try wasm.writeToFile(enabled_features, emit_features_count, arena);2861 try wasm.writeToFile(enabled_features, emit_features_count, arena);
2621}2862}
...@@ -2810,7 +3051,7 @@ fn writeToFile(...@@ -2810,7 +3051,7 @@ fn writeToFile(
2810 if (wasm.function_table.count() > 0) {3051 if (wasm.function_table.count() > 0) {
2811 const header_offset = try reserveVecSectionHeader(&binary_bytes);3052 const header_offset = try reserveVecSectionHeader(&binary_bytes);
28123053
2813 const table_loc = wasm.globals.get(wasm.string_table.getOffset("__indirect_function_table").?).?;3054 const table_loc = wasm.findGlobalSymbol("__indirect_function_table").?;
2814 const table_sym = table_loc.getSymbol(wasm);3055 const table_sym = table_loc.getSymbol(wasm);
28153056
2816 var flags: u32 = if (table_sym.index == 0) 0x0 else 0x02; // passive with implicit 0-index table or set table index manually3057 var flags: u32 = if (table_sym.index == 0) 0x0 else 0x02; // passive with implicit 0-index table or set table index manually
...@@ -2849,10 +3090,12 @@ fn writeToFile(...@@ -2849,10 +3090,12 @@ fn writeToFile(
2849 defer sorted_atoms.deinit();3090 defer sorted_atoms.deinit();
28503091
2851 while (true) {3092 while (true) {
2852 if (!is_obj) {3093 if (wasm.resolved_symbols.contains(atom.symbolLoc())) {
2853 atom.resolveRelocs(wasm);3094 if (!is_obj) {
3095 atom.resolveRelocs(wasm);
3096 }
3097 sorted_atoms.appendAssumeCapacity(atom);
2854 }3098 }
2855 sorted_atoms.appendAssumeCapacity(atom);
2856 atom = atom.next orelse break;3099 atom = atom.next orelse break;
2857 }3100 }
28583101
...@@ -2893,10 +3136,11 @@ fn writeToFile(...@@ -2893,10 +3136,11 @@ fn writeToFile(
2893 // do not output 'bss' section unless we import memory and therefore3136 // do not output 'bss' section unless we import memory and therefore
2894 // want to guarantee the data is zero initialized3137 // want to guarantee the data is zero initialized
2895 if (!import_memory and std.mem.eql(u8, entry.key_ptr.*, ".bss")) continue;3138 if (!import_memory and std.mem.eql(u8, entry.key_ptr.*, ".bss")) continue;
2896 segment_count += 1;
2897 const atom_index = entry.value_ptr.*;3139 const atom_index = entry.value_ptr.*;
2898 var atom: *Atom = wasm.atoms.getPtr(atom_index).?.*.getFirst();
2899 const segment = wasm.segments.items[atom_index];3140 const segment = wasm.segments.items[atom_index];
3141 if (segment.size == 0) continue; // do not emit empty segments
3142 segment_count += 1;
3143 var atom: *Atom = wasm.atoms.getPtr(atom_index).?.*.getFirst();
29003144
2901 // flag and index to memory section (currently, there can only be 1 memory section in wasm)3145 // flag and index to memory section (currently, there can only be 1 memory section in wasm)
2902 try leb.writeULEB128(binary_writer, @as(u32, 0));3146 try leb.writeULEB128(binary_writer, @as(u32, 0));
...@@ -3166,6 +3410,8 @@ fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem...@@ -3166,6 +3410,8 @@ fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem
3166 // bss section is not emitted when this condition holds true, so we also3410 // bss section is not emitted when this condition holds true, so we also
3167 // do not output a name for it.3411 // do not output a name for it.
3168 if (!wasm.base.options.import_memory and std.mem.eql(u8, key, ".bss")) continue;3412 if (!wasm.base.options.import_memory and std.mem.eql(u8, key, ".bss")) continue;
3413 // Synthetic segments are not emitted
3414 if (std.mem.eql(u8, key, ".synthetic")) continue;
3169 segments.appendAssumeCapacity(.{ .index = data_segment_index, .name = key });3415 segments.appendAssumeCapacity(.{ .index = data_segment_index, .name = key });
3170 data_segment_index += 1;3416 data_segment_index += 1;
3171 }3417 }
...@@ -3896,8 +4142,8 @@ pub fn getTypeIndex(wasm: *const Wasm, func_type: std.wasm.Type) ?u32 {...@@ -3896,8 +4142,8 @@ pub fn getTypeIndex(wasm: *const Wasm, func_type: std.wasm.Type) ?u32 {
3896 return null;4142 return null;
3897}4143}
38984144
3899/// Searches for an a matching function signature, when not found4145/// Searches for a matching function signature. When no matching signature is found,
3900/// a new entry will be made. The index of the existing/new signature will be returned.4146/// a new entry will be made. The value returned is the index of the type within `wasm.func_types`.
3901pub fn putOrGetFuncType(wasm: *Wasm, func_type: std.wasm.Type) !u32 {4147pub fn putOrGetFuncType(wasm: *Wasm, func_type: std.wasm.Type) !u32 {
3902 if (wasm.getTypeIndex(func_type)) |index| {4148 if (wasm.getTypeIndex(func_type)) |index| {
3903 return index;4149 return index;
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";