authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-01-11 07:03:15+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-01-12 20:50:18+01:00
logf8d1efd99ab0ff9ae49a17b437814f4fe329e83b
tree411dca0d212597b6498098146a2429a75de04490
parent1072f82acbe976222851bdd357f52dd9659d73d3
signature Commit is signed but in an unrecognized format.

wasm-linker: implement __wasm_call_ctors symbol

This implements the `__wasm_call_ctors` symbol. This symbol is automatically referenced by libc to initialize its constructors. We first retrieve all constructors from each object file, and then create a function body that calls each constructor based on its priority. Constructors are not allowed to have any parameters, but are allowed to have a return type. When a return type does exist, we simply drop its value from the stack after calling the constructor to ensure we pass the stack validator.

2 files changed, 180 insertions(+), 10 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+179-10
...@@ -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();
...@@ -896,6 +937,7 @@ pub fn deinit(wasm: *Wasm) void {...@@ -896,6 +937,7 @@ pub fn deinit(wasm: *Wasm) void {
896 wasm.wasm_globals.deinit(gpa);937 wasm.wasm_globals.deinit(gpa);
897 wasm.function_table.deinit(gpa);938 wasm.function_table.deinit(gpa);
898 wasm.tables.deinit(gpa);939 wasm.tables.deinit(gpa);
940 wasm.init_funcs.deinit(gpa);
899 wasm.exports.deinit(gpa);941 wasm.exports.deinit(gpa);
900942
901 wasm.string_table.deinit(gpa);943 wasm.string_table.deinit(gpa);
...@@ -1698,6 +1740,130 @@ fn sortDataSegments(wasm: *Wasm) !void {...@@ -1698,6 +1740,130 @@ fn sortDataSegments(wasm: *Wasm) !void {
1698 wasm.data_segments = new_mapping;1740 wasm.data_segments = new_mapping;
1699}1741}
17001742
1743/// Obtains all initfuncs from each object file, verifies its function signature,
1744/// and then appends it to our final `init_funcs` list.
1745/// After all functions have been inserted, the functions will be ordered based
1746/// on their priority.
1747/// NOTE: This function must be called before we merged any other section.
1748/// This is because all init funcs in the object files contain references to the
1749/// original functions and their types. We need to know the type to verify it doesn't
1750/// contain any parameters.
1751fn setupInitFunctions(wasm: *Wasm) !void {
1752 for (wasm.objects.items) |object, file_index| {
1753 try wasm.init_funcs.ensureUnusedCapacity(wasm.base.allocator, object.init_funcs.len);
1754 for (object.init_funcs) |init_func| {
1755 const symbol = object.symtable[init_func.symbol_index];
1756 const ty: std.wasm.Type = if (symbol.isUndefined()) ty: {
1757 const imp: types.Import = object.findImport(.function, symbol.index);
1758 break :ty object.func_types[imp.kind.function];
1759 } else ty: {
1760 const func_index = symbol.index - object.importedCountByKind(.function);
1761 const func = object.functions[func_index];
1762 break :ty object.func_types[func.type_index];
1763 };
1764 if (ty.params.len != 0) {
1765 log.err("constructor functions cannot take arguments: '{s}'", .{object.string_table.get(symbol.name)});
1766 return error.InvalidInitFunc;
1767 }
1768 log.debug("appended init func '{s}'\n", .{object.string_table.get(symbol.name)});
1769 wasm.init_funcs.appendAssumeCapacity(.{
1770 .index = init_func.symbol_index,
1771 .file = @intCast(u16, file_index),
1772 .priority = init_func.priority,
1773 });
1774 }
1775 }
1776
1777 // sort the initfunctions based on their priority
1778 std.sort.sort(InitFuncLoc, wasm.init_funcs.items, {}, InitFuncLoc.lessThan);
1779}
1780
1781/// Creates a function body for the `__wasm_call_ctors` symbol.
1782/// Loops over all constructors found in `init_funcs` and calls them
1783/// respectively based on their priority which was sorted by `setupInitFunctions`.
1784/// NOTE: This function must be called after we merged all sections to ensure the
1785/// references to the function stored in the symbol have been finalized so we end
1786/// up calling the resolved function.
1787fn initializeCallCtorsFunction(wasm: *Wasm) !void {
1788 // No code to emit, so also no ctors to call
1789 if (wasm.code_section_index == null) {
1790 // 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.
1792 const loc = wasm.globals.get(wasm.string_table.getOffset("__wasm_call_ctors").?).?;
1793 std.debug.assert(wasm.resolved_symbols.swapRemove(loc));
1794 return;
1795 }
1796
1797 var function_body = std.ArrayList(u8).init(wasm.base.allocator);
1798 defer function_body.deinit();
1799 const writer = function_body.writer();
1800
1801 // Create the function body
1802 {
1803 // Write locals count (we have none)
1804 try leb.writeULEB128(writer, @as(u32, 0));
1805
1806 // call constructors
1807 for (wasm.init_funcs.items) |init_func_loc| {
1808 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];
1815 const ty = wasm.func_types.items[func.type_index];
1816
1817 // Call function by its function index
1818 try writer.writeByte(std.wasm.opcode(.call));
1819 try leb.writeULEB128(writer, symbol.index);
1820
1821 // drop all returned values from the stack as __wasm_call_ctors has no return value
1822 for (ty.returns) |_| {
1823 try writer.writeByte(std.wasm.opcode(.drop));
1824 }
1825 }
1826
1827 // End function body
1828 try writer.writeByte(std.wasm.opcode(.end));
1829 }
1830
1831 const loc = wasm.globals.get(wasm.string_table.getOffset("__wasm_call_ctors").?).?;
1832 const symbol = loc.getSymbol(wasm);
1833 // 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{} });
1835 // create function with above type
1836 const func_index = wasm.imported_functions_count + @intCast(u32, wasm.functions.count());
1837 try wasm.functions.putNoClobber(
1838 wasm.base.allocator,
1839 .{ .file = null, .index = func_index },
1840 .{ .type_index = ty_index },
1841 );
1842 symbol.index = func_index;
1843
1844 // create the atom that will be output into the final binary
1845 const atom = try wasm.base.allocator.create(Atom);
1846 errdefer wasm.base.allocator.destroy(atom);
1847 atom.* = .{
1848 .size = @intCast(u32, function_body.items.len),
1849 .offset = 0,
1850 .sym_index = loc.index,
1851 .file = null,
1852 .alignment = 1,
1853 .next = null,
1854 .prev = null,
1855 .code = function_body.moveToUnmanaged(),
1856 .dbg_info_atom = undefined,
1857 };
1858 try wasm.managed_atoms.append(wasm.base.allocator, atom);
1859 try wasm.appendAtomAtIndex(wasm.code_section_index.?, atom);
1860 try wasm.symbol_atom.putNoClobber(wasm.base.allocator, loc, atom);
1861
1862 // `allocateAtoms` has already been called, set the atom's offset manually.
1863 // This is fine to do manually as we insert the atom at the very end.
1864 atom.offset = atom.prev.?.offset + atom.prev.?.size;
1865}
1866
1701fn setupImports(wasm: *Wasm) !void {1867fn setupImports(wasm: *Wasm) !void {
1702 log.debug("Merging imports", .{});1868 log.debug("Merging imports", .{});
1703 var discarded_it = wasm.discarded.keyIterator();1869 var discarded_it = wasm.discarded.keyIterator();
...@@ -1870,16 +2036,17 @@ fn setupExports(wasm: *Wasm) !void {...@@ -1870,16 +2036,17 @@ fn setupExports(wasm: *Wasm) !void {
18702036
1871 const force_exp_names = wasm.base.options.export_symbol_names;2037 const force_exp_names = wasm.base.options.export_symbol_names;
1872 if (force_exp_names.len > 0) {2038 if (force_exp_names.len > 0) {
1873 var failed_exports = try std.ArrayList([]const u8).initCapacity(wasm.base.allocator, force_exp_names.len);2039 var failed_exports = false;
1874 defer failed_exports.deinit();
18752040
1876 for (force_exp_names) |exp_name| {2041 for (force_exp_names) |exp_name| {
1877 const name_index = wasm.string_table.getOffset(exp_name) orelse {2042 const name_index = wasm.string_table.getOffset(exp_name) orelse {
1878 failed_exports.appendAssumeCapacity(exp_name);2043 log.err("could not export '{s}', symbol not found", .{exp_name});
2044 failed_exports = true;
1879 continue;2045 continue;
1880 };2046 };
1881 const loc = wasm.globals.get(name_index) orelse {2047 const loc = wasm.globals.get(name_index) orelse {
1882 failed_exports.appendAssumeCapacity(exp_name);2048 log.err("could not export '{s}', symbol not found", .{exp_name});
2049 failed_exports = true;
1883 continue;2050 continue;
1884 };2051 };
18852052
...@@ -1887,10 +2054,7 @@ fn setupExports(wasm: *Wasm) !void {...@@ -1887,10 +2054,7 @@ fn setupExports(wasm: *Wasm) !void {
1887 symbol.setFlag(.WASM_SYM_EXPORTED);2054 symbol.setFlag(.WASM_SYM_EXPORTED);
1888 }2055 }
18892056
1890 if (failed_exports.items.len > 0) {2057 if (failed_exports) {
1891 for (failed_exports.items) |exp_name| {
1892 log.err("could not export '{s}', symbol not found", .{exp_name});
1893 }
1894 return error.MissingSymbol;2058 return error.MissingSymbol;
1895 }2059 }
1896 }2060 }
...@@ -1948,6 +2112,7 @@ fn setupStart(wasm: *Wasm) !void {...@@ -1948,6 +2112,7 @@ fn setupStart(wasm: *Wasm) !void {
19482112
1949 const symbol_loc = wasm.globals.get(symbol_name_offset) orelse {2113 const symbol_loc = wasm.globals.get(symbol_name_offset) orelse {
1950 log.err("Entry symbol '{s}' not found", .{entry_name});2114 log.err("Entry symbol '{s}' not found", .{entry_name});
2115 return error.MissingSymbol;
1951 };2116 };
1952 const symbol = symbol_loc.getSymbol(wasm);2117 const symbol = symbol_loc.getSymbol(wasm);
1953 if (symbol.tag != .function) {2118 if (symbol.tag != .function) {
...@@ -2503,6 +2668,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l...@@ -2503,6 +2668,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
2503 try wasm.resolveSymbolsInArchives();2668 try wasm.resolveSymbolsInArchives();
2504 try wasm.checkUndefinedSymbols();2669 try wasm.checkUndefinedSymbols();
25052670
2671 try wasm.setupInitFunctions();
2506 try wasm.setupStart();2672 try wasm.setupStart();
2507 try wasm.setupImports();2673 try wasm.setupImports();
25082674
...@@ -2515,6 +2681,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l...@@ -2515,6 +2681,7 @@ fn linkWithZld(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) l
2515 wasm.mapFunctionTable();2681 wasm.mapFunctionTable();
2516 try wasm.mergeSections();2682 try wasm.mergeSections();
2517 try wasm.mergeTypes();2683 try wasm.mergeTypes();
2684 try wasm.initializeCallCtorsFunction();
2518 try wasm.setupExports();2685 try wasm.setupExports();
2519 try wasm.writeToFile(enabled_features, emit_features_count, arena);2686 try wasm.writeToFile(enabled_features, emit_features_count, arena);
25202687
...@@ -2587,6 +2754,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -2587,6 +2754,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
2587 // When we finish/error we reset the state of the linker2754 // When we finish/error we reset the state of the linker
2588 // So we can rebuild the binary file on each incremental update2755 // So we can rebuild the binary file on each incremental update
2589 defer wasm.resetState();2756 defer wasm.resetState();
2757 try wasm.setupInitFunctions();
2590 try wasm.setupStart();2758 try wasm.setupStart();
2591 try wasm.setupImports();2759 try wasm.setupImports();
2592 if (wasm.base.options.module) |mod| {2760 if (wasm.base.options.module) |mod| {
...@@ -2629,6 +2797,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -2629,6 +2797,7 @@ pub fn flushModule(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
2629 wasm.mapFunctionTable();2797 wasm.mapFunctionTable();
2630 try wasm.mergeSections();2798 try wasm.mergeSections();
2631 try wasm.mergeTypes();2799 try wasm.mergeTypes();
2800 try wasm.initializeCallCtorsFunction();
2632 try wasm.setupExports();2801 try wasm.setupExports();
2633 try wasm.writeToFile(enabled_features, emit_features_count, arena);2802 try wasm.writeToFile(enabled_features, emit_features_count, arena);
2634}2803}
...@@ -3909,8 +4078,8 @@ pub fn getTypeIndex(wasm: *const Wasm, func_type: std.wasm.Type) ?u32 {...@@ -3909,8 +4078,8 @@ pub fn getTypeIndex(wasm: *const Wasm, func_type: std.wasm.Type) ?u32 {
3909 return null;4078 return null;
3910}4079}
39114080
3912/// Searches for an a matching function signature, when not found4081/// Searches for a matching function signature. When no matching signature is found,
3913/// a new entry will be made. The index of the existing/new signature will be returned.4082/// a new entry will be made. The value returned is the index of the type within `wasm.func_types`.
3914pub fn putOrGetFuncType(wasm: *Wasm, func_type: std.wasm.Type) !u32 {4083pub fn putOrGetFuncType(wasm: *Wasm, func_type: std.wasm.Type) !u32 {
3915 if (wasm.getTypeIndex(func_type)) |index| {4084 if (wasm.getTypeIndex(func_type)) |index| {
3916 return index;4085 return index;