From 414fcea162a751435f0194ed4a01785b3a0913a0 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Wed, 24 Aug 2022 17:53:10 +0200 Subject: [PATCH 1/3] link/Wasm: handle extern variables Generate symbols for extern variables and try to resolve them. Unresolved 'data' symbols generate an error as they cannot be exported from the Wasm runtime into a Wasm module. This means, they can only be resolved by other object files such as from other Zig or C code compiled to Wasm. --- src/link/Wasm.zig | 45 ++++++++++++++++++++++++++++++---------- src/link/Wasm/Atom.zig | 5 +---- src/link/Wasm/Symbol.zig | 2 +- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index e20703cb2b371a8725ffe0cfd4da74a08ebd32e1..050d9287a5e7f0aa2e8206c74e4d2d0949e9c60f 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -607,6 +607,24 @@ fn resolveSymbolsInArchives(self: *Wasm) !void { } } +fn checkUndefinedSymbols(self: *const Wasm) !void { + var found_undefined_symbols = false; + for (self.undefs.values()) |undef| { + const symbol = undef.getSymbol(self); + if (symbol.tag == .data) { + found_undefined_symbols = true; + const file_name = if (undef.file) |file_index| name: { + break :name self.objects.items[file_index].name; + } else self.name; + log.err("could not resolve undefined symbol '{s}'", .{undef.getName(self)}); + log.err(" defined in '{s}'", .{file_name}); + } + } + if (found_undefined_symbols) { + return error.UndefinedSymbol; + } +} + pub fn deinit(self: *Wasm) void { const gpa = self.base.allocator; if (build_options.have_llvm) { @@ -783,15 +801,17 @@ pub fn updateDecl(self: *Wasm, mod: *Module, decl_index: Module.Decl.Index) !voi decl.link.wasm.clear(); - if (decl.isExtern()) { - return; - } - if (decl.val.castTag(.function)) |_| { return; } else if (decl.val.castTag(.extern_fn)) |_| { return; } + + if (decl.isExtern()) { + const variable = decl.getVariable().?; + const name = mem.sliceTo(decl.name, 0); + return self.addOrUpdateImport(name, decl.link.wasm.sym_index, variable.lib_name, null); + } const val = if (decl.val.castTag(.variable)) |payload| payload.data.init else decl.val; var code_writer = std.ArrayList(u8).init(self.base.allocator); @@ -834,19 +854,18 @@ pub fn updateDeclLineNumber(self: *Wasm, mod: *Module, decl: *const Module.Decl) } fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, code: []const u8) !void { - if (code.len == 0) return; const mod = self.base.options.module.?; const atom: *Atom = &decl.link.wasm; - atom.size = @intCast(u32, code.len); - atom.alignment = decl.ty.abiAlignment(self.base.options.target); const symbol = &self.symbols.items[atom.sym_index]; - const full_name = try decl.getFullyQualifiedName(mod); defer self.base.allocator.free(full_name); symbol.name = try self.string_table.put(self.base.allocator, full_name); try atom.code.appendSlice(self.base.allocator, code); - try self.resolved_symbols.put(self.base.allocator, atom.symbolLoc(), {}); + + if (code.len == 0) return; + atom.size = @intCast(u32, code.len); + atom.alignment = decl.ty.abiAlignment(self.base.options.target); } /// From a given symbol location, returns its `wasm.GlobalType`. @@ -1235,7 +1254,10 @@ pub fn addOrUpdateImport( .kind = .{ .function = ty_index }, }; } - } else @panic("TODO: Implement undefined symbols for non-function declarations"); + } else { + symbol.tag = .data; + return; // non-functions will not be imported from the runtime, but only resolved during link-time + } } /// Kind represents the type of an Atom, which is only @@ -1438,7 +1460,7 @@ fn setupImports(self: *Wasm) !void { if (std.mem.eql(u8, symbol_loc.getName(self), "__indirect_function_table")) { continue; } - if (symbol.tag == .data or !symbol.requiresImport()) { + if (!symbol.requiresImport()) { continue; } @@ -2007,6 +2029,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod } try self.resolveSymbolsInArchives(); + try self.checkUndefinedSymbols(); // When we finish/error we reset the state of the linker // So we can rebuild the binary file on each incremental update diff --git a/src/link/Wasm/Atom.zig b/src/link/Wasm/Atom.zig index d5bb4509f6c86cc3a7bbf95e7146f4f3044334ca..ed512f466c44519e1d237fd8758e05cf2b180d58 100644 --- a/src/link/Wasm/Atom.zig +++ b/src/link/Wasm/Atom.zig @@ -172,10 +172,7 @@ fn relocationValue(self: Atom, relocation: types.Relocation, wasm_bin: *const Wa .R_WASM_MEMORY_ADDR_SLEB, .R_WASM_MEMORY_ADDR_SLEB64, => { - if (symbol.isUndefined() and symbol.isWeak()) { - return 0; - } - std.debug.assert(symbol.tag == .data); + std.debug.assert(symbol.tag == .data and !symbol.isUndefined()); const merge_segment = wasm_bin.base.options.output_mode != .Obj; const segment_info = if (self.file) |object_index| blk: { break :blk wasm_bin.objects.items[object_index].segment_info; diff --git a/src/link/Wasm/Symbol.zig b/src/link/Wasm/Symbol.zig index fa6ea89d69d9a8b110db9b43519595462f2a8cfd..5e134566053dcd1403794b5fcb3c18557d0e92f0 100644 --- a/src/link/Wasm/Symbol.zig +++ b/src/link/Wasm/Symbol.zig @@ -79,9 +79,9 @@ pub const Flag = enum(u32) { /// Verifies if the given symbol should be imported from the /// host environment or not pub fn requiresImport(self: Symbol) bool { + if (self.tag == .data) return false; if (!self.isUndefined()) return false; if (self.isWeak()) return false; - if (self.tag == .data) return false; // if (self.isDefined() and self.isWeak()) return true; //TODO: Only when building shared lib return true; -- 2.54.0 From 4f72ac265acac682541f170a1189a06350009431 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Fri, 26 Aug 2022 08:11:17 +0200 Subject: [PATCH 2/3] wasm: create relocations for extern decls This also fixes performing relocations for data symbols of which the target symbol exists in an external object file. We do this by checking if the target symbol was discarded, and if so: get the new location so that we can find the corresponding atom that belongs to said new location. Previously it would always assume the symbol would live in the same file as the atom/symbol that is doing the relocation. --- src/arch/wasm/CodeGen.zig | 4 +--- src/link/Wasm/Atom.zig | 9 +++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 95a0a8e4aa83b0adf377de3922392158782ed8d6..e09822c2083b5d28f81bbc472970cb4db984c177 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -2355,7 +2355,7 @@ fn lowerDeclRefValue(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) const module = self.bin_file.base.options.module.?; const decl = module.declPtr(decl_index); - if (decl.ty.zigTypeTag() != .Fn and !decl.ty.hasRuntimeBitsIgnoreComptime()) { + if (!decl.ty.hasRuntimeBitsIgnoreComptime()) { return WValue{ .imm32 = 0xaaaaaaaa }; } @@ -2394,9 +2394,7 @@ fn lowerConstant(self: *Self, val: Value, ty: Type) InnerError!WValue { const decl_index = decl_ref_mut.data.decl_index; return self.lowerDeclRefValue(.{ .ty = ty, .val = val }, decl_index); } - const target = self.target; - switch (ty.zigTypeTag()) { .Void => return WValue{ .none = {} }, .Int => { diff --git a/src/link/Wasm/Atom.zig b/src/link/Wasm/Atom.zig index ed512f466c44519e1d237fd8758e05cf2b180d58..9e7f7a5a76d9a1c66bad2b7e504227d8bbc4e278 100644 --- a/src/link/Wasm/Atom.zig +++ b/src/link/Wasm/Atom.zig @@ -174,13 +174,14 @@ fn relocationValue(self: Atom, relocation: types.Relocation, wasm_bin: *const Wa => { std.debug.assert(symbol.tag == .data and !symbol.isUndefined()); const merge_segment = wasm_bin.base.options.output_mode != .Obj; - const segment_info = if (self.file) |object_index| blk: { + const target_atom_loc = wasm_bin.discarded.get(target_loc) orelse target_loc; + const target_atom = wasm_bin.symbol_atom.get(target_atom_loc).?; + const segment_info = if (target_atom.file) |object_index| blk: { break :blk wasm_bin.objects.items[object_index].segment_info; } else wasm_bin.segment_info.items; const segment_name = segment_info[symbol.index].outputName(merge_segment); - const atom_index = wasm_bin.data_segments.get(segment_name).?; - const target_atom = wasm_bin.symbol_atom.get(target_loc).?; - const segment = wasm_bin.segments.items[atom_index]; + const segment_index = wasm_bin.data_segments.get(segment_name).?; + const segment = wasm_bin.segments.items[segment_index]; return target_atom.offset + segment.offset + (relocation.addend orelse 0); }, .R_WASM_EVENT_INDEX_LEB => return symbol.index, -- 2.54.0 From 8627858bbc2fee848e2f3e3ca64dc944f39591e5 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Fri, 26 Aug 2022 17:29:43 +0200 Subject: [PATCH 3/3] test/link: add test for extern resolution Adds a linker tests to verify extern/undefined symbols representing non-functions are being resolved correctly. --- src/arch/wasm/CodeGen.zig | 2 +- test/link.zig | 6 ++++++ test/link/wasm/extern/build.zig | 17 +++++++++++++++++ test/link/wasm/extern/foo.c | 1 + test/link/wasm/extern/main.zig | 8 ++++++++ 5 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 test/link/wasm/extern/build.zig create mode 100644 test/link/wasm/extern/foo.c create mode 100644 test/link/wasm/extern/main.zig diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index e09822c2083b5d28f81bbc472970cb4db984c177..b9637bf8e31af9bcc9497777cae22958292db036 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -2355,7 +2355,7 @@ fn lowerDeclRefValue(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) const module = self.bin_file.base.options.module.?; const decl = module.declPtr(decl_index); - if (!decl.ty.hasRuntimeBitsIgnoreComptime()) { + if (decl.ty.zigTypeTag() != .Fn and !decl.ty.hasRuntimeBitsIgnoreComptime()) { return WValue{ .imm32 = 0xaaaaaaaa }; } diff --git a/test/link.zig b/test/link.zig index 215a0511fc0f9b585bf343a2ea6e965cd8c58c91..b68353122c31e77e9fbb269198b1c222208598b4 100644 --- a/test/link.zig +++ b/test/link.zig @@ -52,6 +52,12 @@ fn addWasmCases(cases: *tests.StandaloneContext) void { .build_modes = true, .requires_stage2 = true, }); + + cases.addBuildFile("test/link/wasm/extern/build.zig", .{ + .build_modes = true, + .requires_stage2 = true, + .use_emulation = true, + }); } fn addMachOCases(cases: *tests.StandaloneContext) void { diff --git a/test/link/wasm/extern/build.zig b/test/link/wasm/extern/build.zig new file mode 100644 index 0000000000000000000000000000000000000000..88cce88d98954aa51ebddeb040c1cf189dcbd931 --- /dev/null +++ b/test/link/wasm/extern/build.zig @@ -0,0 +1,17 @@ +const std = @import("std"); + +pub fn build(b: *std.build.Builder) void { + const mode = b.standardReleaseOptions(); + const exe = b.addExecutable("extern", "main.zig"); + exe.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .wasi }); + exe.setBuildMode(mode); + exe.addCSourceFile("foo.c", &.{}); + exe.use_llvm = false; + exe.use_lld = false; + + const run = exe.runEmulatable(); + run.expectStdOutEqual("Result: 30"); + + const test_step = b.step("test", "Run linker test"); + test_step.dependOn(&run.step); +} diff --git a/test/link/wasm/extern/foo.c b/test/link/wasm/extern/foo.c new file mode 100644 index 0000000000000000000000000000000000000000..0dafd7e11241911a6f2cdf022d118f1aeec930b8 --- /dev/null +++ b/test/link/wasm/extern/foo.c @@ -0,0 +1 @@ +int foo = 30; diff --git a/test/link/wasm/extern/main.zig b/test/link/wasm/extern/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..b9fa1226eb7ca6f0c57101461a9d252d0ce370a6 --- /dev/null +++ b/test/link/wasm/extern/main.zig @@ -0,0 +1,8 @@ +const std = @import("std"); + +extern const foo: u32; + +pub fn main() void { + const std_out = std.io.getStdOut(); + std_out.writer().print("Result: {d}", .{foo}) catch {}; +} -- 2.54.0