diff --git a/src/Compilation.zig b/src/Compilation.zig
index 371bcdaf187f..e90c6358086a 100644
--- a/src/Compilation.zig
+++ b/src/Compilation.zig
@@ -603,6 +603,11 @@ pub const ClangPreprocessorMode = enum {
     stdout,
 };
 
+pub const WasiExecModel = enum {
+    command,
+    reactor,
+};
+
 pub const InitOptions = struct {
     zig_lib_directory: Directory,
     local_cache_directory: Directory,
@@ -725,6 +730,8 @@ pub const InitOptions = struct {
     test_filter: ?[]const u8 = null,
     test_name_prefix: ?[]const u8 = null,
     subsystem: ?std.Target.SubSystem = null,
+    /// WASI-only. Type of WASI execution model ("command" or "reactor").
+    wasi_exec_model: ?WasiExecModel = null,
 };
 
 fn addPackageTableToCacheHash(
@@ -1340,6 +1347,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,
         });
         errdefer bin_file.destroy();
         comp.* = .{
@@ -1441,9 +1449,14 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
                     .wasi_libc_crt_file = crt_file,
                 });
             }
-            // TODO add logic deciding which crt1 we want here.
+            const crt_file: wasi_libc.CRTFile = if (comp.bin_file.options.wasi_exec_model) |exec_model| crt_file: {
+                switch (exec_model) {
+                    .command => break :crt_file wasi_libc.CRTFile.crt1_command_o,
+                    .reactor => break :crt_file wasi_libc.CRTFile.crt1_reactor_o,
+                }
+            } else .crt1_o;
             comp.work_queue.writeAssumeCapacity(&[_]Job{
-                .{ .wasi_libc_crt_file = .crt1_o },
+                .{ .wasi_libc_crt_file = crt_file },
                 .{ .wasi_libc_crt_file = .libc_a },
             });
         }
@@ -1868,7 +1881,7 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
 
             for (keys[1..]) |key, i| {
                 err_msg.notes[i] = .{
-                    .src_loc = key.nodeOffsetSrcLoc(values[i+1]),
+                    .src_loc = key.nodeOffsetSrcLoc(values[i + 1]),
                     .msg = "also here",
                 };
             }
diff --git a/src/clang_options_data.zig b/src/clang_options_data.zig
index c800de3edf74..be4e5c13cbc4 100644
--- a/src/clang_options_data.zig
+++ b/src/clang_options_data.zig
@@ -5257,7 +5257,14 @@ jspd1("fxray-modes="),
     .psl = false,
 },
 jspd1("iwithsysroot"),
-joinpd1("mexec-model="),
+.{
+    .name = "mexec-model=",
+    .syntax = .joined,
+    .zig_equivalent = .exec_model,
+    .pd1 = true,
+    .pd2 = false,
+    .psl = false,
+},
 joinpd1("mharden-sls="),
 joinpd1("mhvx-length="),
 jspd1("objc-isystem"),
diff --git a/src/link.zig b/src/link.zig
index 4072e90ca3e0..534b39ebbb65 100644
--- a/src/link.zig
+++ b/src/link.zig
@@ -118,6 +118,9 @@ pub const Options = struct {
     version: ?std.builtin.Version,
     libc_installation: ?*const LibCInstallation,
 
+    /// WASI-only. Type of WASI execution model ("command" or "reactor").
+    wasi_exec_model: ?Compilation.WasiExecModel = null,
+
     pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {
         return if (options.use_lld) .Obj else options.output_mode;
     }
diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig
index 21bb9d826cba..bfe1902826de 100644
--- a/src/link/Wasm.zig
+++ b/src/link/Wasm.zig
@@ -681,6 +681,12 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
             // Put stack before globals so that stack overflow results in segfault immediately
             // 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 != .reactor) break :blk;
+                try argv.append("--no-entry");
+            }
         } else {
             try argv.append("--no-entry"); // So lld doesn't look for _start.
             try argv.append("--export-all");
@@ -692,10 +698,11 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
         });
 
         if (target.os.tag == .wasi) {
-            if (self.base.options.link_libc and self.base.options.output_mode == .Exe) {
-                // TODO work out if we want standard crt, a reactor or a command
-                try argv.append(try comp.get_libc_crt_file(arena, "crt1.o"));
-            }
+            const crt_name = if (self.base.options.wasi_exec_model) |exec_model|
+                try std.fmt.allocPrint(arena, "crt1-{s}.o", .{@tagName(exec_model)})
+            else
+                "crt1.o";
+            try argv.append(try comp.get_libc_crt_file(arena, crt_name));
 
             const is_exe_or_dyn_lib = self.base.options.output_mode == .Exe or
                 (self.base.options.output_mode == .Lib and self.base.options.link_mode == .Dynamic);
diff --git a/src/main.zig b/src/main.zig
index 0b93f977261b..8b1c640cfb0b 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -613,6 +613,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: ?Compilation.WasiExecModel = null;
 
     var system_libs = std.ArrayList([]const u8).init(gpa);
     defer system_libs.deinit();
@@ -1254,6 +1255,13 @@ fn buildOutputType(
                     .framework => try frameworks.append(it.only_arg),
                     .nostdlibinc => want_native_include_dirs = false,
                     .strip => strip = true,
+                    .exec_model => {
+                        if (std.mem.eql(u8, it.only_arg, "reactor")) {
+                            wasi_exec_model = Compilation.WasiExecModel.reactor;
+                        } else if (std.mem.eql(u8, it.only_arg, "command")) {
+                            wasi_exec_model = Compilation.WasiExecModel.command;
+                        }
+                    },
                 }
             }
             // Parse linker args.
@@ -1969,6 +1977,7 @@ fn buildOutputType(
         .test_name_prefix = test_name_prefix,
         .disable_lld_caching = !have_enable_cache,
         .subsystem = subsystem,
+        .wasi_exec_model = wasi_exec_model,
     }) catch |err| {
         fatal("unable to create compilation: {s}", .{@errorName(err)});
     };
@@ -3341,6 +3350,7 @@ pub const ClangArgIterator = struct {
         red_zone,
         no_red_zone,
         strip,
+        exec_model,
     };
 
     const Args = struct {
diff --git a/tools/update_clang_options.zig b/tools/update_clang_options.zig
index 95c635c09d8b..6f986ee6dffb 100644
--- a/tools/update_clang_options.zig
+++ b/tools/update_clang_options.zig
@@ -336,6 +336,10 @@ const known_options = [_]KnownOpt{
         .name = "dynamiclib",
         .ident = "shared",
     },
+    .{
+        .name = "mexec-model",
+        .ident = "exec_model",
+    }
 };
 
 const blacklisted_options = [_][]const u8{};
