authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-08-30 16:59:48+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-11-03 12:48:52+01:00
log1cb7a01b25349c42dbe207b68f9e19b92890b3d2
tree602589918ad620910c8462711492e1ffff6c2826
parent94cee4fb27a433824c2540dc37375dc14befdf47
signaturelock-open Commit is signed but in an unrecognized format.

wasm-linker: implement `-fno-entry` flag

This adds support for the `-fno-entry` and `-fentry` flags respectively, for zig build-{exe/lib} and the build system. For `zig cc` we use the `--no-entry` flag to be compatible with clang and existing tooling. In `start.zig` we now make the main function optional when the target is WebAssembly, as to allow for the build-exe command in combination with `-fno-entry`. When the execution model is set, and is set to 'reactor', we now verify when an entry name is given it matches what is expected. When no entry point is given, we set it to `_initialize` by default. This means the user will also be met with an error when they use the reactor model, but did not provide the correct function.

6 files changed, 47 insertions(+), 21 deletions(-)

lib/std/Build/Step/Compile.zig+3
...@@ -64,6 +64,8 @@ initial_memory: ?u64 = null,...@@ -64,6 +64,8 @@ initial_memory: ?u64 = null,
64max_memory: ?u64 = null,64max_memory: ?u64 = null,
65shared_memory: bool = false,65shared_memory: bool = false,
66global_base: ?u64 = null,66global_base: ?u64 = null,
67/// For WebAssembly only. Tells the linker to not output an entry point.
68no_entry: ?bool = null,
67c_std: std.Build.CStd,69c_std: std.Build.CStd,
68/// Set via options; intended to be read-only after that.70/// Set via options; intended to be read-only after that.
69zig_lib_dir: ?LazyPath,71zig_lib_dir: ?LazyPath,
...@@ -1851,6 +1853,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -1851,6 +1853,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
1851 if (self.global_base) |global_base| {1853 if (self.global_base) |global_base| {
1852 try zig_args.append(b.fmt("--global-base={d}", .{global_base}));1854 try zig_args.append(b.fmt("--global-base={d}", .{global_base}));
1853 }1855 }
1856 try addFlag(&zig_args, "entry", self.no_entry);
18541857
1855 if (self.code_model != .default) {1858 if (self.code_model != .default) {
1856 try zig_args.append("-mcmodel");1859 try zig_args.append("-mcmodel");
lib/std/start.zig+6-2
...@@ -82,11 +82,15 @@ comptime {...@@ -82,11 +82,15 @@ comptime {
82 .reactor => "_initialize",82 .reactor => "_initialize",
83 .command => "_start",83 .command => "_start",
84 };84 };
85 if (!@hasDecl(root, wasm_start_sym)) {85 if (!@hasDecl(root, wasm_start_sym) and @hasDecl(root, "main")) {
86 // Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which
87 // case it's not required to provide an entrypoint such as main.
86 @export(wasi_start, .{ .name = wasm_start_sym });88 @export(wasi_start, .{ .name = wasm_start_sym });
87 }89 }
88 } else if (native_arch.isWasm() and native_os == .freestanding) {90 } else if (native_arch.isWasm() and native_os == .freestanding) {
89 if (!@hasDecl(root, start_sym_name)) @export(wasm_freestanding_start, .{ .name = start_sym_name });91 // Only call main when defined. For WebAssembly it's allowed to pass `-fno-entry` in which
92 // case it's not required to provide an entrypoint such as main.
93 if (!@hasDecl(root, start_sym_name) and @hasDecl(root, "main")) @export(wasm_freestanding_start, .{ .name = start_sym_name });
90 } else if (native_os != .other and native_os != .freestanding) {94 } else if (native_os != .other and native_os != .freestanding) {
91 if (!@hasDecl(root, start_sym_name)) @export(_start, .{ .name = start_sym_name });95 if (!@hasDecl(root, start_sym_name)) @export(_start, .{ .name = start_sym_name });
92 }96 }
src/Compilation.zig+3
...@@ -643,6 +643,7 @@ pub const InitOptions = struct {...@@ -643,6 +643,7 @@ pub const InitOptions = struct {
643 linker_import_symbols: bool = false,643 linker_import_symbols: bool = false,
644 linker_import_table: bool = false,644 linker_import_table: bool = false,
645 linker_export_table: bool = false,645 linker_export_table: bool = false,
646 linker_no_entry: bool = false,
646 linker_initial_memory: ?u64 = null,647 linker_initial_memory: ?u64 = null,
647 linker_max_memory: ?u64 = null,648 linker_max_memory: ?u64 = null,
648 linker_shared_memory: bool = false,649 linker_shared_memory: bool = false,
...@@ -1614,6 +1615,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {...@@ -1614,6 +1615,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
1614 .import_symbols = options.linker_import_symbols,1615 .import_symbols = options.linker_import_symbols,
1615 .import_table = options.linker_import_table,1616 .import_table = options.linker_import_table,
1616 .export_table = options.linker_export_table,1617 .export_table = options.linker_export_table,
1618 .no_entry = options.linker_no_entry,
1617 .initial_memory = options.linker_initial_memory,1619 .initial_memory = options.linker_initial_memory,
1618 .max_memory = options.linker_max_memory,1620 .max_memory = options.linker_max_memory,
1619 .shared_memory = options.linker_shared_memory,1621 .shared_memory = options.linker_shared_memory,
...@@ -2577,6 +2579,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes...@@ -2577,6 +2579,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
2577 man.hash.addOptional(comp.bin_file.options.max_memory);2579 man.hash.addOptional(comp.bin_file.options.max_memory);
2578 man.hash.add(comp.bin_file.options.shared_memory);2580 man.hash.add(comp.bin_file.options.shared_memory);
2579 man.hash.addOptional(comp.bin_file.options.global_base);2581 man.hash.addOptional(comp.bin_file.options.global_base);
2582 man.hash.add(comp.bin_file.options.no_entry);
25802583
2581 // Mach-O specific stuff2584 // Mach-O specific stuff
2582 man.hash.addListOfBytes(comp.bin_file.options.framework_dirs);2585 man.hash.addListOfBytes(comp.bin_file.options.framework_dirs);
src/link.zig+1
...@@ -166,6 +166,7 @@ pub const Options = struct {...@@ -166,6 +166,7 @@ pub const Options = struct {
166 export_table: bool,166 export_table: bool,
167 initial_memory: ?u64,167 initial_memory: ?u64,
168 max_memory: ?u64,168 max_memory: ?u64,
169 no_entry: bool,
169 shared_memory: bool,170 shared_memory: bool,
170 export_symbol_names: []const []const u8,171 export_symbol_names: []const []const u8,
171 global_base: ?u64,172 global_base: ?u64,
src/link/Wasm.zig+5-19
...@@ -2817,14 +2817,10 @@ fn setupExports(wasm: *Wasm) !void {...@@ -2817,14 +2817,10 @@ fn setupExports(wasm: *Wasm) !void {
2817}2817}
28182818
2819fn setupStart(wasm: *Wasm) !void {2819fn setupStart(wasm: *Wasm) !void {
2820 if (wasm.base.options.no_entry) return;
2820 const entry_name = wasm.base.options.entry orelse "_start";2821 const entry_name = wasm.base.options.entry orelse "_start";
28212822
2822 const symbol_loc = wasm.findGlobalSymbol(entry_name) orelse {2823 const symbol_loc = wasm.findGlobalSymbol(entry_name) orelse {
2823 if (wasm.base.options.output_mode == .Exe) {
2824 if (wasm.base.options.wasi_exec_model == .reactor) return; // Not required for reactors
2825 } else {
2826 return; // No entry point needed for non-executable wasm files
2827 }
2828 log.err("Entry symbol '{s}' missing", .{entry_name});2824 log.err("Entry symbol '{s}' missing", .{entry_name});
2829 return error.MissingSymbol;2825 return error.MissingSymbol;
2830 };2826 };
...@@ -4544,24 +4540,14 @@ fn linkWithLLD(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !...@@ -4544,24 +4540,14 @@ fn linkWithLLD(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !
4544 const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size});4540 const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size});
4545 try argv.append(arg);4541 try argv.append(arg);
45464542
4547 if (wasm.base.options.output_mode == .Exe) {
4548 if (wasm.base.options.wasi_exec_model == .reactor) {
4549 // Reactor execution model does not have _start so lld doesn't look for it.
4550 try argv.append("--no-entry");
4551 // Make sure "_initialize" and other used-defined functions are exported if this is WASI reactor.
4552 // If rdynamic is true, it will already be appended, so only verify if the user did not specify
4553 // the flag in which case, we ensure `--export-dynamic` is called.
4554 if (!wasm.base.options.rdynamic) {
4555 try argv.append("--export-dynamic");
4556 }
4557 }
4558 } else if (wasm.base.options.entry == null) {
4559 try argv.append("--no-entry"); // So lld doesn't look for _start.
4560 }
4561 if (wasm.base.options.import_symbols) {4543 if (wasm.base.options.import_symbols) {
4562 try argv.append("--allow-undefined");4544 try argv.append("--allow-undefined");
4563 }4545 }
45644546
4547 if (wasm.base.options.no_entry) {
4548 try argv.append("--no-entry");
4549 }
4550
4565 // XXX - TODO: add when wasm-ld supports --build-id.4551 // XXX - TODO: add when wasm-ld supports --build-id.
4566 // if (wasm.base.options.build_id) {4552 // if (wasm.base.options.build_id) {
4567 // try argv.append("--build-id=tree");4553 // try argv.append("--build-id=tree");
src/main.zig+29
...@@ -577,6 +577,8 @@ const usage_build_generic =...@@ -577,6 +577,8 @@ const usage_build_generic =
577 \\ --shared-memory (WebAssembly) use shared linear memory577 \\ --shared-memory (WebAssembly) use shared linear memory
578 \\ --global-base=[addr] (WebAssembly) where to start to place global data578 \\ --global-base=[addr] (WebAssembly) where to start to place global data
579 \\ --export=[value] (WebAssembly) Force a symbol to be exported579 \\ --export=[value] (WebAssembly) Force a symbol to be exported
580 \\ -fentry (WebAssembly) Force output an entry point
581 \\ -fno-entry (WebAssembly) Do not output any entry point
580 \\582 \\
581 \\Test Options:583 \\Test Options:
582 \\ --test-filter [text] Skip tests that do not match filter584 \\ --test-filter [text] Skip tests that do not match filter
...@@ -835,6 +837,7 @@ fn buildOutputType(...@@ -835,6 +837,7 @@ fn buildOutputType(
835 var linker_import_symbols: bool = false;837 var linker_import_symbols: bool = false;
836 var linker_import_table: bool = false;838 var linker_import_table: bool = false;
837 var linker_export_table: bool = false;839 var linker_export_table: bool = false;
840 var linker_no_entry: ?bool = null;
838 var linker_initial_memory: ?u64 = null;841 var linker_initial_memory: ?u64 = null;
839 var linker_max_memory: ?u64 = null;842 var linker_max_memory: ?u64 = null;
840 var linker_shared_memory: bool = false;843 var linker_shared_memory: bool = false;
...@@ -1503,6 +1506,10 @@ fn buildOutputType(...@@ -1503,6 +1506,10 @@ fn buildOutputType(
1503 }1506 }
1504 } else if (mem.eql(u8, arg, "--import-memory")) {1507 } else if (mem.eql(u8, arg, "--import-memory")) {
1505 linker_import_memory = true;1508 linker_import_memory = true;
1509 } else if (mem.eql(u8, arg, "-fentry")) {
1510 linker_no_entry = false;
1511 } else if (mem.eql(u8, arg, "-fno-entry")) {
1512 linker_no_entry = true;
1506 } else if (mem.eql(u8, arg, "--export-memory")) {1513 } else if (mem.eql(u8, arg, "--export-memory")) {
1507 linker_export_memory = true;1514 linker_export_memory = true;
1508 } else if (mem.eql(u8, arg, "--import-symbols")) {1515 } else if (mem.eql(u8, arg, "--import-symbols")) {
...@@ -2134,6 +2141,8 @@ fn buildOutputType(...@@ -2134,6 +2141,8 @@ fn buildOutputType(
2134 linker_import_table = true;2141 linker_import_table = true;
2135 } else if (mem.eql(u8, arg, "--export-table")) {2142 } else if (mem.eql(u8, arg, "--export-table")) {
2136 linker_export_table = true;2143 linker_export_table = true;
2144 } else if (mem.eql(u8, arg, "--no-entry")) {
2145 linker_no_entry = true;
2137 } else if (mem.eql(u8, arg, "--initial-memory")) {2146 } else if (mem.eql(u8, arg, "--initial-memory")) {
2138 const next_arg = linker_args_it.nextOrFatal();2147 const next_arg = linker_args_it.nextOrFatal();
2139 linker_initial_memory = std.fmt.parseUnsigned(u32, eatIntPrefix(next_arg, 16), 16) catch |err| {2148 linker_initial_memory = std.fmt.parseUnsigned(u32, eatIntPrefix(next_arg, 16), 16) catch |err| {
...@@ -2618,6 +2627,25 @@ fn buildOutputType(...@@ -2618,6 +2627,25 @@ fn buildOutputType(
2618 if (single_threaded == null) {2627 if (single_threaded == null) {
2619 single_threaded = true;2628 single_threaded = true;
2620 }2629 }
2630 if (wasi_exec_model) |model| {
2631 if (model == .reactor) {
2632 if (linker_no_entry != null and !linker_no_entry.?) {
2633 fatal("WASI exucution model 'reactor' incompatible with flag '-fentry'. Reactor execution model has no entry point", .{});
2634 }
2635 if (entry) |entry_name| {
2636 if (!mem.eql(u8, "_initialize", entry_name)) {
2637 fatal("the entry symbol of the reactor model must be '_initialize', but found '{s}'", .{entry_name});
2638 }
2639 } else {
2640 entry = "_initialize";
2641 }
2642 }
2643 }
2644 if (linker_no_entry) |no_entry| {
2645 if (no_entry and entry != null) {
2646 fatal("combination of '--entry' and `-fno-entry` are incompatible", .{});
2647 }
2648 }
2621 if (linker_shared_memory) {2649 if (linker_shared_memory) {
2622 if (output_mode == .Obj) {2650 if (output_mode == .Obj) {
2623 fatal("shared memory is not allowed in object files", .{});2651 fatal("shared memory is not allowed in object files", .{});
...@@ -3465,6 +3493,7 @@ fn buildOutputType(...@@ -3465,6 +3493,7 @@ fn buildOutputType(
3465 .linker_import_symbols = linker_import_symbols,3493 .linker_import_symbols = linker_import_symbols,
3466 .linker_import_table = linker_import_table,3494 .linker_import_table = linker_import_table,
3467 .linker_export_table = linker_export_table,3495 .linker_export_table = linker_export_table,
3496 .linker_no_entry = linker_no_entry orelse false,
3468 .linker_initial_memory = linker_initial_memory,3497 .linker_initial_memory = linker_initial_memory,
3469 .linker_max_memory = linker_max_memory,3498 .linker_max_memory = linker_max_memory,
3470 .linker_shared_memory = linker_shared_memory,3499 .linker_shared_memory = linker_shared_memory,