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,
6464max_memory: ?u64 = null,
6565shared_memory: bool = false,
6666global_base: ?u64 = null,
67/// For WebAssembly only. Tells the linker to not output an entry point.
68no_entry: ?bool = null,
6769c_std: std.Build.CStd,
6870/// Set via options; intended to be read-only after that.
6971zig_lib_dir: ?LazyPath,
......@@ -1851,6 +1853,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
18511853 if (self.global_base) |global_base| {
18521854 try zig_args.append(b.fmt("--global-base={d}", .{global_base}));
18531855 }
1856 try addFlag(&zig_args, "entry", self.no_entry);
18541857
18551858 if (self.code_model != .default) {
18561859 try zig_args.append("-mcmodel");
lib/std/start.zig+6-2
......@@ -82,11 +82,15 @@ comptime {
8282 .reactor => "_initialize",
8383 .command => "_start",
8484 };
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.
8688 @export(wasi_start, .{ .name = wasm_start_sym });
8789 }
8890 } 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 });
9094 } else if (native_os != .other and native_os != .freestanding) {
9195 if (!@hasDecl(root, start_sym_name)) @export(_start, .{ .name = start_sym_name });
9296 }
src/Compilation.zig+3
......@@ -643,6 +643,7 @@ pub const InitOptions = struct {
643643 linker_import_symbols: bool = false,
644644 linker_import_table: bool = false,
645645 linker_export_table: bool = false,
646 linker_no_entry: bool = false,
646647 linker_initial_memory: ?u64 = null,
647648 linker_max_memory: ?u64 = null,
648649 linker_shared_memory: bool = false,
......@@ -1614,6 +1615,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
16141615 .import_symbols = options.linker_import_symbols,
16151616 .import_table = options.linker_import_table,
16161617 .export_table = options.linker_export_table,
1618 .no_entry = options.linker_no_entry,
16171619 .initial_memory = options.linker_initial_memory,
16181620 .max_memory = options.linker_max_memory,
16191621 .shared_memory = options.linker_shared_memory,
......@@ -2577,6 +2579,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
25772579 man.hash.addOptional(comp.bin_file.options.max_memory);
25782580 man.hash.add(comp.bin_file.options.shared_memory);
25792581 man.hash.addOptional(comp.bin_file.options.global_base);
2582 man.hash.add(comp.bin_file.options.no_entry);
25802583
25812584 // Mach-O specific stuff
25822585 man.hash.addListOfBytes(comp.bin_file.options.framework_dirs);
src/link.zig+1
......@@ -166,6 +166,7 @@ pub const Options = struct {
166166 export_table: bool,
167167 initial_memory: ?u64,
168168 max_memory: ?u64,
169 no_entry: bool,
169170 shared_memory: bool,
170171 export_symbol_names: []const []const u8,
171172 global_base: ?u64,
src/link/Wasm.zig+5-19
......@@ -2817,14 +2817,10 @@ fn setupExports(wasm: *Wasm) !void {
28172817}
28182818
28192819fn setupStart(wasm: *Wasm) !void {
2820 if (wasm.base.options.no_entry) return;
28202821 const entry_name = wasm.base.options.entry orelse "_start";
28212822
28222823 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 }
28282824 log.err("Entry symbol '{s}' missing", .{entry_name});
28292825 return error.MissingSymbol;
28302826 };
......@@ -4544,24 +4540,14 @@ fn linkWithLLD(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !
45444540 const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size});
45454541 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 }
45614543 if (wasm.base.options.import_symbols) {
45624544 try argv.append("--allow-undefined");
45634545 }
45644546
4547 if (wasm.base.options.no_entry) {
4548 try argv.append("--no-entry");
4549 }
4550
45654551 // XXX - TODO: add when wasm-ld supports --build-id.
45664552 // if (wasm.base.options.build_id) {
45674553 // try argv.append("--build-id=tree");
src/main.zig+29
......@@ -577,6 +577,8 @@ const usage_build_generic =
577577 \\ --shared-memory (WebAssembly) use shared linear memory
578578 \\ --global-base=[addr] (WebAssembly) where to start to place global data
579579 \\ --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
580582 \\
581583 \\Test Options:
582584 \\ --test-filter [text] Skip tests that do not match filter
......@@ -835,6 +837,7 @@ fn buildOutputType(
835837 var linker_import_symbols: bool = false;
836838 var linker_import_table: bool = false;
837839 var linker_export_table: bool = false;
840 var linker_no_entry: ?bool = null;
838841 var linker_initial_memory: ?u64 = null;
839842 var linker_max_memory: ?u64 = null;
840843 var linker_shared_memory: bool = false;
......@@ -1503,6 +1506,10 @@ fn buildOutputType(
15031506 }
15041507 } else if (mem.eql(u8, arg, "--import-memory")) {
15051508 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;
15061513 } else if (mem.eql(u8, arg, "--export-memory")) {
15071514 linker_export_memory = true;
15081515 } else if (mem.eql(u8, arg, "--import-symbols")) {
......@@ -2134,6 +2141,8 @@ fn buildOutputType(
21342141 linker_import_table = true;
21352142 } else if (mem.eql(u8, arg, "--export-table")) {
21362143 linker_export_table = true;
2144 } else if (mem.eql(u8, arg, "--no-entry")) {
2145 linker_no_entry = true;
21372146 } else if (mem.eql(u8, arg, "--initial-memory")) {
21382147 const next_arg = linker_args_it.nextOrFatal();
21392148 linker_initial_memory = std.fmt.parseUnsigned(u32, eatIntPrefix(next_arg, 16), 16) catch |err| {
......@@ -2618,6 +2627,25 @@ fn buildOutputType(
26182627 if (single_threaded == null) {
26192628 single_threaded = true;
26202629 }
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 }
26212649 if (linker_shared_memory) {
26222650 if (output_mode == .Obj) {
26232651 fatal("shared memory is not allowed in object files", .{});
......@@ -3465,6 +3493,7 @@ fn buildOutputType(
34653493 .linker_import_symbols = linker_import_symbols,
34663494 .linker_import_table = linker_import_table,
34673495 .linker_export_table = linker_export_table,
3496 .linker_no_entry = linker_no_entry orelse false,
34683497 .linker_initial_memory = linker_initial_memory,
34693498 .linker_max_memory = linker_max_memory,
34703499 .linker_shared_memory = linker_shared_memory,