Commit bc7761d8e0

Takeshi Yoneda <takeshi@tetrate.io>
2021-07-01 02:02:48
Add support for WASI reactor in pure Zig-exe. (#9178)
* Add command line help for "-mexec-model" * Define WasmExecModel enum in std.builtin. * Drop the support for the old crt1.o in favor of crt1-command.o Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
1 parent a95ba0d
lib/std/builtin.zig
@@ -457,6 +457,13 @@ pub const LinkMode = enum {
     Dynamic,
 };
 
+/// This data structure is used by the Zig language code generation and
+/// therefore must be kept in sync with the compiler implementation.
+pub const WasiExecModel = enum {
+    command,
+    reactor,
+};
+
 /// This data structure is used by the Zig language code generation and
 /// therefore must be kept in sync with the compiler implementation.
 pub const Version = struct {
lib/std/start.zig
@@ -65,8 +65,9 @@ comptime {
                 }
             } else if (native_os == .uefi) {
                 if (!@hasDecl(root, "EfiMain")) @export(EfiMain, .{ .name = "EfiMain" });
-            } else if (native_arch.isWasm() and native_os == .freestanding) {
-                if (!@hasDecl(root, start_sym_name)) @export(wasm_freestanding_start, .{ .name = start_sym_name });
+            } else if (native_arch.isWasm()) {
+                const wasm_start_sym = if (builtin.wasi_exec_model == .reactor) "_initialize" else "_start";
+                if (!@hasDecl(root, wasm_start_sym)) @export(wasm_start, .{ .name = wasm_start_sym });
             } else if (native_os != .other and native_os != .freestanding) {
                 if (!@hasDecl(root, start_sym_name)) @export(_start, .{ .name = start_sym_name });
             }
@@ -135,10 +136,21 @@ fn _DllMainCRTStartup(
     return std.os.windows.TRUE;
 }
 
-fn wasm_freestanding_start() callconv(.C) void {
-    // This is marked inline because for some reason LLVM in release mode fails to inline it,
+fn wasm_start() callconv(.C) void {
+    // The entrypoint is marked inline because for some reason LLVM in release mode fails to inline it,
     // and we want fewer call frames in stack traces.
-    _ = @call(.{ .modifier = .always_inline }, callMain, .{});
+    switch (native_os) {
+        .freestanding => {
+            _ = @call(.{ .modifier = .always_inline }, callMain, .{});
+        },
+        .wasi => {
+            switch (builtin.wasi_exec_model) {
+                .reactor => _ = @call(.{ .modifier = .always_inline }, callMain, .{}),
+                .command => std.os.wasi.proc_exit(@call(.{ .modifier = .always_inline }, callMain, .{})),
+            }
+        },
+        else => @compileError("unsupported OS"),
+    }
 }
 
 fn EfiMain(handle: uefi.Handle, system_table: *uefi.tables.SystemTable) callconv(.C) usize {
@@ -164,12 +176,6 @@ fn EfiMain(handle: uefi.Handle, system_table: *uefi.tables.SystemTable) callconv
 }
 
 fn _start() callconv(.Naked) noreturn {
-    if (native_os == .wasi) {
-        // This is marked inline because for some reason LLVM in release mode fails to inline it,
-        // and we want fewer call frames in stack traces.
-        std.os.wasi.proc_exit(@call(.{ .modifier = .always_inline }, callMain, .{}));
-    }
-
     switch (native_arch) {
         .x86_64 => {
             argc_argv_ptr = asm volatile (
src/link/Wasm.zig
@@ -687,10 +687,15 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
             // before corrupting globals. See https://github.com/ziglang/zig/issues/4496
             try argv.append("--stack-first");
 
-            // Reactor execution model does not have _start so lld doesn't look for it.
-            if (self.base.options.wasi_exec_model) |exec_model| blk: {
-                if (exec_model != .crt1_reactor_o) break :blk;
+            if (self.base.options.wasi_exec_model == .reactor) {
+                // Reactor execution model does not have _start so lld doesn't look for it.
                 try argv.append("--no-entry");
+                // Make sure "_initialize" is exported even if this is pure Zig WASI reactor
+                // where WASM_SYMBOL_EXPORTED flag in LLVM is not set on _initialize.
+                try argv.appendSlice(&[_][]const u8{
+                    "--export",
+                    "_initialize",
+                });
             }
         } else {
             try argv.append("--no-entry"); // So lld doesn't look for _start.
@@ -717,7 +722,7 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
                 if (self.base.options.link_libc) {
                     try argv.append(try comp.get_libc_crt_file(
                         arena,
-                        wasi_libc.crtFileFullName(self.base.options.wasi_exec_model orelse .crt1_o),
+                        wasi_libc.execModelCrtFileFullName(self.base.options.wasi_exec_model),
                     ));
                     try argv.append(try comp.get_libc_crt_file(arena, "libc.a"));
                 }
src/Compilation.zig
@@ -724,7 +724,7 @@ pub const InitOptions = struct {
     test_name_prefix: ?[]const u8 = null,
     subsystem: ?std.Target.SubSystem = null,
     /// WASI-only. Type of WASI execution model ("command" or "reactor").
-    wasi_exec_model: ?wasi_libc.CRTFile = null,
+    wasi_exec_model: ?std.builtin.WasiExecModel = null,
 };
 
 fn addPackageTableToCacheHash(
@@ -790,6 +790,9 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
 
     const needs_c_symbols = !options.skip_linker_dependencies and is_exe_or_dyn_lib;
 
+    // WASI-only. Resolve the optinal exec-model option, defaults to command.
+    const wasi_exec_model = if (options.target.os.tag != .wasi) undefined else options.wasi_exec_model orelse .command;
+
     const comp: *Compilation = comp: {
         // For allocations that have the same lifetime as Compilation. This arena is used only during this
         // initialization and then is freed in deinit().
@@ -1340,7 +1343,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             .disable_lld_caching = options.disable_lld_caching,
             .subsystem = options.subsystem,
             .is_test = options.is_test,
-            .wasi_exec_model = options.wasi_exec_model,
+            .wasi_exec_model = wasi_exec_model,
         });
         errdefer bin_file.destroy();
         comp.* = .{
@@ -1442,7 +1445,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
                 });
             }
             comp.work_queue.writeAssumeCapacity(&[_]Job{
-                .{ .wasi_libc_crt_file = comp.bin_file.options.wasi_exec_model orelse .crt1_o },
+                .{ .wasi_libc_crt_file = wasi_libc.execModelCrtFile(wasi_exec_model) },
                 .{ .wasi_libc_crt_file = .libc_a },
             });
         }
@@ -3650,6 +3653,14 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) Alloc
         std.zig.fmtId(@tagName(comp.bin_file.options.machine_code_model)),
     });
 
+    if (target.os.tag == .wasi) {
+        const wasi_exec_model_fmt = std.zig.fmtId(@tagName(comp.bin_file.options.wasi_exec_model));
+        try buffer.writer().print(
+            \\pub const wasi_exec_model = std.builtin.WasiExecModel.{};
+            \\
+        , .{wasi_exec_model_fmt});
+    }
+
     if (comp.bin_file.options.is_test) {
         try buffer.appendSlice(
             \\pub var test_functions: []std.builtin.TestFn = undefined; // overwritten later
src/link.zig
@@ -116,7 +116,7 @@ pub const Options = struct {
     libc_installation: ?*const LibCInstallation,
 
     /// WASI-only. Type of WASI execution model ("command" or "reactor").
-    wasi_exec_model: ?wasi_libc.CRTFile = null,
+    wasi_exec_model: std.builtin.WasiExecModel = undefined,
 
     pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {
         return if (options.use_lld) .Obj else options.output_mode;
src/main.zig
@@ -321,6 +321,7 @@ const usage_build_generic =
     \\            medium|large]
     \\  -mred-zone                Force-enable the "red-zone"
     \\  -mno-red-zone             Force-disable the "red-zone"
+    \\  -mexec-model=[value]      Execution model (WASI only)
     \\  --name [name]             Override root name (not a file path)
     \\  -O [mode]                 Choose what to optimize for
     \\    Debug                   (default) Optimizations off, safety on
@@ -618,7 +619,7 @@ fn buildOutputType(
     var subsystem: ?std.Target.SubSystem = null;
     var major_subsystem_version: ?u32 = null;
     var minor_subsystem_version: ?u32 = null;
-    var wasi_exec_model: ?wasi_libc.CRTFile = null;
+    var wasi_exec_model: ?std.builtin.WasiExecModel = null;
 
     var system_libs = std.ArrayList([]const u8).init(gpa);
     defer system_libs.deinit();
@@ -1071,6 +1072,10 @@ fn buildOutputType(
                         mem.startsWith(u8, arg, "-I"))
                     {
                         try clang_argv.append(arg);
+                    } else if (mem.startsWith(u8, arg, "-mexec-model=")) {
+                        wasi_exec_model = std.meta.stringToEnum(std.builtin.WasiExecModel, arg["-mexec-model=".len..]) orelse {
+                            fatal("expected [command|reactor] for -mexec-mode=[value], found '{s}'", .{arg["-mexec-model=".len..]});
+                        };
                     } else {
                         fatal("unrecognized parameter: '{s}'", .{arg});
                     }
@@ -1277,11 +1282,9 @@ fn buildOutputType(
                     .nostdlibinc => want_native_include_dirs = false,
                     .strip => strip = true,
                     .exec_model => {
-                        if (std.mem.eql(u8, it.only_arg, "reactor")) {
-                            wasi_exec_model = .crt1_reactor_o;
-                        } else if (std.mem.eql(u8, it.only_arg, "command")) {
-                            wasi_exec_model = .crt1_command_o;
-                        }
+                        wasi_exec_model = std.meta.stringToEnum(std.builtin.WasiExecModel, it.only_arg) orelse {
+                            fatal("expected [command|reactor] for -mexec-mode=[value], found '{s}'", .{it.only_arg});
+                        };
                     },
                 }
             }
src/wasi_libc.zig
@@ -45,9 +45,15 @@ pub fn emulatedLibCRFileLibName(crt_file: CRTFile) []const u8 {
     };
 }
 
-pub fn crtFileFullName(crt_file: CRTFile) []const u8 {
-    return switch (crt_file) {
-        .crt1_o => "crt1.o",
+pub fn execModelCrtFile(wasi_exec_model: std.builtin.WasiExecModel) CRTFile {
+    return switch (wasi_exec_model) {
+        .reactor => CRTFile.crt1_reactor_o,
+        .command => CRTFile.crt1_command_o,
+    };
+}
+
+pub fn execModelCrtFileFullName(wasi_exec_model: std.builtin.WasiExecModel) []const u8 {
+    return switch (execModelCrtFile(wasi_exec_model)) {
         .crt1_reactor_o => "crt1-reactor.o",
         .crt1_command_o => "crt1-command.o",
         else => unreachable,