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 {...@@ -1491,6 +1491,8 @@ pub const LibExeObjStep = struct {
1491 test_evented_io: bool = false,1491 test_evented_io: bool = false,
1492 code_model: std.builtin.CodeModel = .default,1492 code_model: std.builtin.CodeModel = .default,
1493 wasi_exec_model: ?std.builtin.WasiExecModel = null,1493 wasi_exec_model: ?std.builtin.WasiExecModel = null,
1494 /// Symbols to be exported when compiling to wasm
1495 export_symbol_names: []const []const u8 = &.{},
14941496
1495 root_src: ?FileSource,1497 root_src: ?FileSource,
1496 out_h_filename: []const u8,1498 out_h_filename: []const u8,
...@@ -2511,6 +2513,9 @@ pub const LibExeObjStep = struct {...@@ -2511,6 +2513,9 @@ pub const LibExeObjStep = struct {
2511 if (self.wasi_exec_model) |model| {2513 if (self.wasi_exec_model) |model| {
2512 try zig_args.append(builder.fmt("-mexec-model={s}", .{@tagName(model)}));2514 try zig_args.append(builder.fmt("-mexec-model={s}", .{@tagName(model)}));
2513 }2515 }
2516 for (self.export_symbol_names) |symbol_name| {
2517 try zig_args.append(builder.fmt("--export={s}", .{symbol_name}));
2518 }
25142519
2515 if (!self.target.isNative()) {2520 if (!self.target.isNative()) {
2516 try zig_args.append("-target");2521 try zig_args.append("-target");
src/Compilation.zig+2
...@@ -727,6 +727,7 @@ pub const InitOptions = struct {...@@ -727,6 +727,7 @@ pub const InitOptions = struct {
727 linker_initial_memory: ?u64 = null,727 linker_initial_memory: ?u64 = null,
728 linker_max_memory: ?u64 = null,728 linker_max_memory: ?u64 = null,
729 linker_global_base: ?u64 = null,729 linker_global_base: ?u64 = null,
730 linker_export_symbol_names: []const []const u8 = &.{},
730 each_lib_rpath: ?bool = null,731 each_lib_rpath: ?bool = null,
731 disable_c_depfile: bool = false,732 disable_c_depfile: bool = false,
732 linker_z_nodelete: bool = false,733 linker_z_nodelete: bool = false,
...@@ -1457,6 +1458,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {...@@ -1457,6 +1458,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
1457 .initial_memory = options.linker_initial_memory,1458 .initial_memory = options.linker_initial_memory,
1458 .max_memory = options.linker_max_memory,1459 .max_memory = options.linker_max_memory,
1459 .global_base = options.linker_global_base,1460 .global_base = options.linker_global_base,
1461 .export_symbol_names = options.linker_export_symbol_names,
1460 .z_nodelete = options.linker_z_nodelete,1462 .z_nodelete = options.linker_z_nodelete,
1461 .z_notext = options.linker_z_notext,1463 .z_notext = options.linker_z_notext,
1462 .z_defs = options.linker_z_defs,1464 .z_defs = options.linker_z_defs,
src/link.zig+1
...@@ -104,6 +104,7 @@ pub const Options = struct {...@@ -104,6 +104,7 @@ pub const Options = struct {
104 import_memory: bool,104 import_memory: bool,
105 initial_memory: ?u64,105 initial_memory: ?u64,
106 max_memory: ?u64,106 max_memory: ?u64,
107 export_symbol_names: []const []const u8,
107 global_base: ?u64,108 global_base: ?u64,
108 is_native_os: bool,109 is_native_os: bool,
109 is_native_abi: bool,110 is_native_abi: bool,
src/link/Wasm.zig+25-2
...@@ -1011,6 +1011,10 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {...@@ -1011,6 +1011,10 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
1011 man.hash.addOptional(self.base.options.initial_memory);1011 man.hash.addOptional(self.base.options.initial_memory);
1012 man.hash.addOptional(self.base.options.max_memory);1012 man.hash.addOptional(self.base.options.max_memory);
1013 man.hash.addOptional(self.base.options.global_base);1013 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
1015 // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.1019 // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
1016 _ = try man.hit();1020 _ = try man.hit();
...@@ -1103,6 +1107,16 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {...@@ -1103,6 +1107,16 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
1103 try argv.append(arg);1107 try argv.append(arg);
1104 }1108 }
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
1106 if (self.base.options.output_mode == .Exe) {1120 if (self.base.options.output_mode == .Exe) {
1107 // Increase the default stack size to a more reasonable value of 1MB instead of1121 // Increase the default stack size to a more reasonable value of 1MB instead of
1108 // the default of 1 Wasm page being 64KB, unless overridden by the user.1122 // the default of 1 Wasm page being 64KB, unless overridden by the user.
...@@ -1119,7 +1133,11 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {...@@ -1119,7 +1133,11 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
1119 // Reactor execution model does not have _start so lld doesn't look for it.1133 // Reactor execution model does not have _start so lld doesn't look for it.
1120 try argv.append("--no-entry");1134 try argv.append("--no-entry");
1121 // Make sure "_initialize" and other used-defined functions are exported if this is WASI reactor.1135 // 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 }
1123 }1141 }
1124 } else {1142 } else {
1125 if (self.base.options.stack_size_override) |stack_size| {1143 if (self.base.options.stack_size_override) |stack_size| {
...@@ -1127,8 +1145,13 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {...@@ -1127,8 +1145,13 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
1127 const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size});1145 const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size});
1128 try argv.append(arg);1146 try argv.append(arg);
1129 }1147 }
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 }
1130 try argv.append("--no-entry"); // So lld doesn't look for _start.1154 try argv.append("--no-entry"); // So lld doesn't look for _start.
1131 try argv.append("--export-all");
1132 }1155 }
1133 try argv.appendSlice(&[_][]const u8{1156 try argv.appendSlice(&[_][]const u8{
1134 "--allow-undefined",1157 "--allow-undefined",
src/main.zig+9
...@@ -434,6 +434,7 @@ const usage_build_generic =...@@ -434,6 +434,7 @@ const usage_build_generic =
434 \\ --initial-memory=[bytes] (WebAssembly) initial size of the linear memory434 \\ --initial-memory=[bytes] (WebAssembly) initial size of the linear memory
435 \\ --max-memory=[bytes] (WebAssembly) maximum size of the linear memory435 \\ --max-memory=[bytes] (WebAssembly) maximum size of the linear memory
436 \\ --global-base=[addr] (WebAssembly) where to start to place global data436 \\ --global-base=[addr] (WebAssembly) where to start to place global data
437 \\ --export=[value] (WebAssembly) Force a symbol to be exported
437 \\438 \\
438 \\Test Options:439 \\Test Options:
439 \\ --test-filter [text] Skip tests that do not match filter440 \\ --test-filter [text] Skip tests that do not match filter
...@@ -711,6 +712,9 @@ fn buildOutputType(...@@ -711,6 +712,9 @@ fn buildOutputType(
711 var test_exec_args = std.ArrayList(?[]const u8).init(gpa);712 var test_exec_args = std.ArrayList(?[]const u8).init(gpa);
712 defer test_exec_args.deinit();713 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
714 // This package only exists to clean up the code parsing --pkg-begin and718 // This package only exists to clean up the code parsing --pkg-begin and
715 // --pkg-end flags. Use dummy values that are safe for the destroy call.719 // --pkg-end flags. Use dummy values that are safe for the destroy call.
716 var pkg_tree_root: Package = .{720 var pkg_tree_root: Package = .{
...@@ -1175,6 +1179,8 @@ fn buildOutputType(...@@ -1175,6 +1179,8 @@ fn buildOutputType(
1175 linker_max_memory = parseIntSuffix(arg, "--max-memory=".len);1179 linker_max_memory = parseIntSuffix(arg, "--max-memory=".len);
1176 } else if (mem.startsWith(u8, arg, "--global-base=")) {1180 } else if (mem.startsWith(u8, arg, "--global-base=")) {
1177 linker_global_base = parseIntSuffix(arg, "--global-base=".len);1181 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..]);
1178 } else if (mem.eql(u8, arg, "-Bsymbolic")) {1184 } else if (mem.eql(u8, arg, "-Bsymbolic")) {
1179 linker_bind_global_refs_locally = true;1185 linker_bind_global_refs_locally = true;
1180 } else if (mem.eql(u8, arg, "--debug-compile-errors")) {1186 } else if (mem.eql(u8, arg, "--debug-compile-errors")) {
...@@ -1554,6 +1560,8 @@ fn buildOutputType(...@@ -1554,6 +1560,8 @@ fn buildOutputType(
1554 linker_max_memory = parseIntSuffix(arg, "--max-memory=".len);1560 linker_max_memory = parseIntSuffix(arg, "--max-memory=".len);
1555 } else if (mem.startsWith(u8, arg, "--global-base=")) {1561 } else if (mem.startsWith(u8, arg, "--global-base=")) {
1556 linker_global_base = parseIntSuffix(arg, "--global-base=".len);1562 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..]);
1557 } else if (mem.eql(u8, arg, "-z")) {1565 } else if (mem.eql(u8, arg, "-z")) {
1558 i += 1;1566 i += 1;
1559 if (i >= linker_args.items.len) {1567 if (i >= linker_args.items.len) {
...@@ -2438,6 +2446,7 @@ fn buildOutputType(...@@ -2438,6 +2446,7 @@ fn buildOutputType(
2438 .linker_initial_memory = linker_initial_memory,2446 .linker_initial_memory = linker_initial_memory,
2439 .linker_max_memory = linker_max_memory,2447 .linker_max_memory = linker_max_memory,
2440 .linker_global_base = linker_global_base,2448 .linker_global_base = linker_global_base,
2449 .linker_export_symbol_names = linker_export_symbol_names.items,
2441 .linker_z_nodelete = linker_z_nodelete,2450 .linker_z_nodelete = linker_z_nodelete,
2442 .linker_z_notext = linker_z_notext,2451 .linker_z_notext = linker_z_notext,
2443 .linker_z_defs = linker_z_defs,2452 .linker_z_defs = linker_z_defs,