authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-12-12 16:21:25+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-12-14 14:02:23-08:00
log50201e1c30e0d71bafd643e9804d55eceb7b3542
treef600a02163b1b3bb52bb9df28413df1035d4672f
parente563b166b2b70975899c84beb425c8739d05ed65

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.

5 files changed, 42 insertions(+), 2 deletions(-)

lib/std/build.zig+5
......@@ -1491,6 +1491,8 @@ pub const LibExeObjStep = struct {
14911491 test_evented_io: bool = false,
14921492 code_model: std.builtin.CodeModel = .default,
14931493 wasi_exec_model: ?std.builtin.WasiExecModel = null,
1494 /// Symbols to be exported when compiling to wasm
1495 export_symbol_names: []const []const u8 = &.{},
14941496
14951497 root_src: ?FileSource,
14961498 out_h_filename: []const u8,
......@@ -2511,6 +2513,9 @@ pub const LibExeObjStep = struct {
25112513 if (self.wasi_exec_model) |model| {
25122514 try zig_args.append(builder.fmt("-mexec-model={s}", .{@tagName(model)}));
25132515 }
2516 for (self.export_symbol_names) |symbol_name| {
2517 try zig_args.append(builder.fmt("--export={s}", .{symbol_name}));
2518 }
25142519
25152520 if (!self.target.isNative()) {
25162521 try zig_args.append("-target");
src/Compilation.zig+2
......@@ -727,6 +727,7 @@ pub const InitOptions = struct {
727727 linker_initial_memory: ?u64 = null,
728728 linker_max_memory: ?u64 = null,
729729 linker_global_base: ?u64 = null,
730 linker_export_symbol_names: []const []const u8 = &.{},
730731 each_lib_rpath: ?bool = null,
731732 disable_c_depfile: bool = false,
732733 linker_z_nodelete: bool = false,
......@@ -1457,6 +1458,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
14571458 .initial_memory = options.linker_initial_memory,
14581459 .max_memory = options.linker_max_memory,
14591460 .global_base = options.linker_global_base,
1461 .export_symbol_names = options.linker_export_symbol_names,
14601462 .z_nodelete = options.linker_z_nodelete,
14611463 .z_notext = options.linker_z_notext,
14621464 .z_defs = options.linker_z_defs,
src/link.zig+1
......@@ -104,6 +104,7 @@ pub const Options = struct {
104104 import_memory: bool,
105105 initial_memory: ?u64,
106106 max_memory: ?u64,
107 export_symbol_names: []const []const u8,
107108 global_base: ?u64,
108109 is_native_os: bool,
109110 is_native_abi: bool,
src/link/Wasm.zig+25-2
......@@ -1011,6 +1011,10 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
10111011 man.hash.addOptional(self.base.options.initial_memory);
10121012 man.hash.addOptional(self.base.options.max_memory);
10131013 man.hash.addOptional(self.base.options.global_base);
1014 man.hash.add(self.base.options.export_symbol_names.len);
1015 for (self.base.options.export_symbol_names) |symbol_name| {
1016 man.hash.addBytes(symbol_name);
1017 }
10141018
10151019 // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
10161020 _ = try man.hit();
......@@ -1103,6 +1107,16 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
11031107 try argv.append(arg);
11041108 }
11051109
1110 // Users are allowed to specify which symbols they want to export to the wasm host.
1111 for (self.base.options.export_symbol_names) |symbol_name| {
1112 const arg = try std.fmt.allocPrint(arena, "--export={s}", .{symbol_name});
1113 try argv.append(arg);
1114 }
1115
1116 if (self.base.options.rdynamic) {
1117 try argv.append("--export-dynamic");
1118 }
1119
11061120 if (self.base.options.output_mode == .Exe) {
11071121 // Increase the default stack size to a more reasonable value of 1MB instead of
11081122 // the default of 1 Wasm page being 64KB, unless overridden by the user.
......@@ -1119,7 +1133,11 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
11191133 // Reactor execution model does not have _start so lld doesn't look for it.
11201134 try argv.append("--no-entry");
11211135 // Make sure "_initialize" and other used-defined functions are exported if this is WASI reactor.
1122 try argv.append("--export-dynamic");
1136 // If rdynamic is true, it will already be appended, so only verify if the user did not specify
1137 // the flag in which case, we ensure `--export-dynamic` is called.
1138 if (!self.base.options.rdynamic) {
1139 try argv.append("--export-dynamic");
1140 }
11231141 }
11241142 } else {
11251143 if (self.base.options.stack_size_override) |stack_size| {
......@@ -1127,8 +1145,13 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
11271145 const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size});
11281146 try argv.append(arg);
11291147 }
1148
1149 // Only when the user has not specified how they want to export the symbols, do we want
1150 // to export all symbols.
1151 if (self.base.options.export_symbol_names.len == 0 and !self.base.options.rdynamic) {
1152 try argv.append("--export-all");
1153 }
11301154 try argv.append("--no-entry"); // So lld doesn't look for _start.
1131 try argv.append("--export-all");
11321155 }
11331156 try argv.appendSlice(&[_][]const u8{
11341157 "--allow-undefined",
src/main.zig+9
......@@ -434,6 +434,7 @@ const usage_build_generic =
434434 \\ --initial-memory=[bytes] (WebAssembly) initial size of the linear memory
435435 \\ --max-memory=[bytes] (WebAssembly) maximum size of the linear memory
436436 \\ --global-base=[addr] (WebAssembly) where to start to place global data
437 \\ --export=[value] (WebAssembly) Force a symbol to be exported
437438 \\
438439 \\Test Options:
439440 \\ --test-filter [text] Skip tests that do not match filter
......@@ -711,6 +712,9 @@ fn buildOutputType(
711712 var test_exec_args = std.ArrayList(?[]const u8).init(gpa);
712713 defer test_exec_args.deinit();
713714
715 var linker_export_symbol_names = std.ArrayList([]const u8).init(gpa);
716 defer linker_export_symbol_names.deinit();
717
714718 // This package only exists to clean up the code parsing --pkg-begin and
715719 // --pkg-end flags. Use dummy values that are safe for the destroy call.
716720 var pkg_tree_root: Package = .{
......@@ -1175,6 +1179,8 @@ fn buildOutputType(
11751179 linker_max_memory = parseIntSuffix(arg, "--max-memory=".len);
11761180 } else if (mem.startsWith(u8, arg, "--global-base=")) {
11771181 linker_global_base = parseIntSuffix(arg, "--global-base=".len);
1182 } else if (mem.startsWith(u8, arg, "--export=")) {
1183 try linker_export_symbol_names.append(arg["--export=".len..]);
11781184 } else if (mem.eql(u8, arg, "-Bsymbolic")) {
11791185 linker_bind_global_refs_locally = true;
11801186 } else if (mem.eql(u8, arg, "--debug-compile-errors")) {
......@@ -1554,6 +1560,8 @@ fn buildOutputType(
15541560 linker_max_memory = parseIntSuffix(arg, "--max-memory=".len);
15551561 } else if (mem.startsWith(u8, arg, "--global-base=")) {
15561562 linker_global_base = parseIntSuffix(arg, "--global-base=".len);
1563 } else if (mem.startsWith(u8, arg, "--export=")) {
1564 try linker_export_symbol_names.append(arg["--export=".len..]);
15571565 } else if (mem.eql(u8, arg, "-z")) {
15581566 i += 1;
15591567 if (i >= linker_args.items.len) {
......@@ -2438,6 +2446,7 @@ fn buildOutputType(
24382446 .linker_initial_memory = linker_initial_memory,
24392447 .linker_max_memory = linker_max_memory,
24402448 .linker_global_base = linker_global_base,
2449 .linker_export_symbol_names = linker_export_symbol_names.items,
24412450 .linker_z_nodelete = linker_z_nodelete,
24422451 .linker_z_notext = linker_z_notext,
24432452 .linker_z_defs = linker_z_defs,