From 50201e1c30e0d71bafd643e9804d55eceb7b3542 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sun, 12 Dec 2021 16:21:25 +0100 Subject: [PATCH] wasm-linker: Allow specifying symbols to be exported Notating a symbol to be exported in code will only tell the linker where to find this symbol, so other object files can find it. However, this does not mean said symbol will also be exported to the host environment. Currently, we 'fix' this by force exporting every single symbol that is visible. This creates bigger binaries and means host environments have access to symbols that they perhaps shouldn't have. Now, users can tell Zig which symbols are to be exported, meaning all other symbols that are not specified will not be exported. Another change is we now support `-rdynamic` in the wasm linker as well, meaning all symbols will be put in the dynamic symbol table. This is the same behavior as with ELF. This means there's a 3rd strategy users will have to build their wasm binary. --- lib/std/build.zig | 5 +++++ src/Compilation.zig | 2 ++ src/link.zig | 1 + src/link/Wasm.zig | 27 +++++++++++++++++++++++++-- src/main.zig | 9 +++++++++ 5 files changed, 42 insertions(+), 2 deletions(-) diff --git a/lib/std/build.zig b/lib/std/build.zig index e3e3041dbb64c5cdf98d16be892a670f946843e3..e5a6e6e471940ac98f88f631e4bbc284dfaa7baf 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -1491,6 +1491,8 @@ pub const LibExeObjStep = struct { test_evented_io: bool = false, code_model: std.builtin.CodeModel = .default, wasi_exec_model: ?std.builtin.WasiExecModel = null, + /// Symbols to be exported when compiling to wasm + export_symbol_names: []const []const u8 = &.{}, root_src: ?FileSource, out_h_filename: []const u8, @@ -2511,6 +2513,9 @@ pub const LibExeObjStep = struct { if (self.wasi_exec_model) |model| { try zig_args.append(builder.fmt("-mexec-model={s}", .{@tagName(model)})); } + for (self.export_symbol_names) |symbol_name| { + try zig_args.append(builder.fmt("--export={s}", .{symbol_name})); + } if (!self.target.isNative()) { try zig_args.append("-target"); diff --git a/src/Compilation.zig b/src/Compilation.zig index 81c3d0132f98cbdfe24250fafeee73bee10a5ac1..d58394de65cd5bf7fd28b18bb8dd2fc854ae651a 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -727,6 +727,7 @@ pub const InitOptions = struct { linker_initial_memory: ?u64 = null, linker_max_memory: ?u64 = null, linker_global_base: ?u64 = null, + linker_export_symbol_names: []const []const u8 = &.{}, each_lib_rpath: ?bool = null, disable_c_depfile: bool = false, linker_z_nodelete: bool = false, @@ -1457,6 +1458,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { .initial_memory = options.linker_initial_memory, .max_memory = options.linker_max_memory, .global_base = options.linker_global_base, + .export_symbol_names = options.linker_export_symbol_names, .z_nodelete = options.linker_z_nodelete, .z_notext = options.linker_z_notext, .z_defs = options.linker_z_defs, diff --git a/src/link.zig b/src/link.zig index 79a1c634624cadf0340ba6410a44e5933db257f9..17083a915ffa885e4ac870d96d2d85d4a565cd03 100644 --- a/src/link.zig +++ b/src/link.zig @@ -104,6 +104,7 @@ pub const Options = struct { import_memory: bool, initial_memory: ?u64, max_memory: ?u64, + export_symbol_names: []const []const u8, global_base: ?u64, is_native_os: bool, is_native_abi: bool, diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 12afc7164cffbb3367f935b8156ec95acdd20465..cde0cdf890c72e0793ce1e336ae3cb0367dacbb8 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -1011,6 +1011,10 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { man.hash.addOptional(self.base.options.initial_memory); man.hash.addOptional(self.base.options.max_memory); man.hash.addOptional(self.base.options.global_base); + man.hash.add(self.base.options.export_symbol_names.len); + for (self.base.options.export_symbol_names) |symbol_name| { + man.hash.addBytes(symbol_name); + } // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. _ = try man.hit(); @@ -1103,6 +1107,16 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { try argv.append(arg); } + // Users are allowed to specify which symbols they want to export to the wasm host. + for (self.base.options.export_symbol_names) |symbol_name| { + const arg = try std.fmt.allocPrint(arena, "--export={s}", .{symbol_name}); + try argv.append(arg); + } + + if (self.base.options.rdynamic) { + try argv.append("--export-dynamic"); + } + if (self.base.options.output_mode == .Exe) { // Increase the default stack size to a more reasonable value of 1MB instead of // the default of 1 Wasm page being 64KB, unless overridden by the user. @@ -1119,7 +1133,11 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { // Reactor execution model does not have _start so lld doesn't look for it. try argv.append("--no-entry"); // Make sure "_initialize" and other used-defined functions are exported if this is WASI reactor. - try argv.append("--export-dynamic"); + // If rdynamic is true, it will already be appended, so only verify if the user did not specify + // the flag in which case, we ensure `--export-dynamic` is called. + if (!self.base.options.rdynamic) { + try argv.append("--export-dynamic"); + } } } else { if (self.base.options.stack_size_override) |stack_size| { @@ -1127,8 +1145,13 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size}); try argv.append(arg); } + + // Only when the user has not specified how they want to export the symbols, do we want + // to export all symbols. + if (self.base.options.export_symbol_names.len == 0 and !self.base.options.rdynamic) { + try argv.append("--export-all"); + } try argv.append("--no-entry"); // So lld doesn't look for _start. - try argv.append("--export-all"); } try argv.appendSlice(&[_][]const u8{ "--allow-undefined", diff --git a/src/main.zig b/src/main.zig index 761935c8977ed902ff2fdf97df8003b9e117f732..57342702eeca25dbfd6b8c0b8f991b26b0c2803f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -434,6 +434,7 @@ const usage_build_generic = \\ --initial-memory=[bytes] (WebAssembly) initial size of the linear memory \\ --max-memory=[bytes] (WebAssembly) maximum size of the linear memory \\ --global-base=[addr] (WebAssembly) where to start to place global data + \\ --export=[value] (WebAssembly) Force a symbol to be exported \\ \\Test Options: \\ --test-filter [text] Skip tests that do not match filter @@ -711,6 +712,9 @@ fn buildOutputType( var test_exec_args = std.ArrayList(?[]const u8).init(gpa); defer test_exec_args.deinit(); + var linker_export_symbol_names = std.ArrayList([]const u8).init(gpa); + defer linker_export_symbol_names.deinit(); + // This package only exists to clean up the code parsing --pkg-begin and // --pkg-end flags. Use dummy values that are safe for the destroy call. var pkg_tree_root: Package = .{ @@ -1175,6 +1179,8 @@ fn buildOutputType( linker_max_memory = parseIntSuffix(arg, "--max-memory=".len); } else if (mem.startsWith(u8, arg, "--global-base=")) { linker_global_base = parseIntSuffix(arg, "--global-base=".len); + } else if (mem.startsWith(u8, arg, "--export=")) { + try linker_export_symbol_names.append(arg["--export=".len..]); } else if (mem.eql(u8, arg, "-Bsymbolic")) { linker_bind_global_refs_locally = true; } else if (mem.eql(u8, arg, "--debug-compile-errors")) { @@ -1554,6 +1560,8 @@ fn buildOutputType( linker_max_memory = parseIntSuffix(arg, "--max-memory=".len); } else if (mem.startsWith(u8, arg, "--global-base=")) { linker_global_base = parseIntSuffix(arg, "--global-base=".len); + } else if (mem.startsWith(u8, arg, "--export=")) { + try linker_export_symbol_names.append(arg["--export=".len..]); } else if (mem.eql(u8, arg, "-z")) { i += 1; if (i >= linker_args.items.len) { @@ -2438,6 +2446,7 @@ fn buildOutputType( .linker_initial_memory = linker_initial_memory, .linker_max_memory = linker_max_memory, .linker_global_base = linker_global_base, + .linker_export_symbol_names = linker_export_symbol_names.items, .linker_z_nodelete = linker_z_nodelete, .linker_z_notext = linker_z_notext, .linker_z_defs = linker_z_defs, -- 2.54.0