diff --git a/src/backend/dev/mod.zig b/src/backend/dev/mod.zig index 8c2c2d3732b..60ee4452ede 100644 --- a/src/backend/dev/mod.zig +++ b/src/backend/dev/mod.zig @@ -18,11 +18,13 @@ pub const EvalBackend = enum { dev, interpreter, llvm, + wasm, pub fn fromString(s: []const u8) ?EvalBackend { if (std.mem.eql(u8, s, "dev")) return .dev; if (std.mem.eql(u8, s, "interpreter")) return .interpreter; if (std.mem.eql(u8, s, "llvm")) return .llvm; + if (std.mem.eql(u8, s, "wasm")) return .wasm; return null; } }; diff --git a/src/build/modules.zig b/src/build/modules.zig index 432156f937a..0a86145c869 100644 --- a/src/build/modules.zig +++ b/src/build/modules.zig @@ -336,7 +336,7 @@ pub const ModuleType = enum { .values => &.{ .collections, .base, .builtins, .layout }, .interpreter_values => &.{ .collections, .base, .builtins, .interpreter_layout }, .eval => &.{ .tracy, .io, .collections, .base, .types, .builtins, .parse, .can, .check, .layout, .interpreter_layout, .values, .interpreter_values, .build_options, .reporting, .backend, .mir, .lir, .roc_target, .sljmp }, - .compile => &.{ .tracy, .build_options, .io, .builtins, .collections, .base, .types, .parse, .can, .check, .reporting, .layout, .eval, .unbundle, .roc_target }, + .compile => &.{ .tracy, .build_options, .io, .builtins, .collections, .base, .types, .parse, .can, .check, .reporting, .layout, .eval, .unbundle, .roc_target, .sljmp }, .ipc => &.{}, .repl => &.{ .base, .collections, .compile, .parse, .types, .can, .check, .builtins, .layout, .values, .eval, .backend, .roc_target }, .fmt => &.{ .base, .parse, .collections, .can, .io, .tracy }, @@ -649,9 +649,9 @@ pub const RocModules = struct { .optimize = optimize, // IPC module needs libc for mmap, munmap, close on POSIX systems // Bundle module needs libc for C zstd (unbundle uses stdlib zstd) - // Eval/repl modules need libc for setjmp/longjmp crash protection + // Eval/repl/compile modules need libc for setjmp/longjmp crash protection // sljmp module needs libc for setjmp/longjmp functions - .link_libc = (module_type == .ipc or module_type == .bundle or module_type == .eval or module_type == .repl or module_type == .sljmp), + .link_libc = (module_type == .ipc or module_type == .bundle or module_type == .eval or module_type == .repl or module_type == .sljmp or module_type == .compile), }), .filters = filter_injection.filters, }); diff --git a/src/cli/main.zig b/src/cli/main.zig index b1a79a298d1..b73dcf89d1c 100644 --- a/src/cli/main.zig +++ b/src/cli/main.zig @@ -958,7 +958,7 @@ fn rocRun(ctx: *CliContext, args: cli_args.RunArgs) !void { } switch (args.opt.toBackend()) { - .dev, .llvm => return rocRunDevShim(ctx, args), + .dev, .llvm, .wasm => return rocRunDevShim(ctx, args), .interpreter => {}, } @@ -1864,40 +1864,22 @@ fn rocRunDefaultApp(ctx: *CliContext, args: cli_args.RunArgs, original_source: [ var cli_args_list = echo_platform.buildCliArgs(args.app_args, &roc_ops); var result_buf: [16]u8 align(16) = undefined; - switch (args.opt.toBackend()) { - .dev, .llvm => { - runViaDev( - ctx.gpa, - entry.platform_env, - resolved.all_module_envs, - entry.app_module_env, - entry.entrypoint_expr, - &roc_ops, - @ptrCast(&cli_args_list), - @ptrCast(&result_buf), - ) catch |err| { - std.debug.print("Dev backend execution error: {}\n", .{err}); - std.process.exit(1); - }; - }, - .interpreter => { - compile.runner.runViaInterpreter( - ctx.gpa, - entry.platform_env, - build_env.builtin_modules, - resolved.all_module_envs, - entry.app_module_env, - entry.entrypoint_expr, - &roc_ops, - @ptrCast(&cli_args_list), - @ptrCast(&result_buf), - target, - ) catch |err| { - std.debug.print("Execution error: {}\n", .{err}); - std.process.exit(1); - }; - }, - } + _ = compile.runner.run( + args.opt.toBackend(), + ctx.gpa, + entry.platform_env, + build_env.builtin_modules, + resolved.all_module_envs, + entry.app_module_env, + entry.entrypoint_expr, + &roc_ops, + @ptrCast(&cli_args_list), + @ptrCast(&result_buf), + target, + ) catch |err| { + std.debug.print("Execution error: {}\n", .{err}); + std.process.exit(1); + }; // Platform returns I8; bit-identical to u8 for std.process.exit const exit_code = result_buf[0]; @@ -3964,7 +3946,7 @@ fn rocBuild(ctx: *CliContext, args: cli_args.BuildArgs) !void { // Select build path based on optimization level switch (args.opt.toBackend()) { - .dev, .llvm => { + .dev, .llvm, .wasm => { // Use native code generation backend try rocBuildNative(ctx, args); }, @@ -5592,7 +5574,7 @@ fn rocTest(ctx: *CliContext, args: cli_args.TestArgs) !void { // Run tests using the selected backend switch (args.opt.toBackend()) { - .dev, .llvm => { + .dev, .llvm, .wasm => { // Run tests using dev backend (native code generation) var dev_eval = eval.DevEvaluator.init(ctx.gpa, null) catch |err| { try stderr.print("Failed to create dev evaluator: {}\n", .{err}); @@ -6035,92 +6017,6 @@ fn rocGlue(ctx: *CliContext, args: cli_args.GlueArgs) glue.GlueError!void { }, temp_dir); } -/// Run a compiled Roc entrypoint through the dev backend (native code generation). -/// Resolves entrypoint layouts, JIT-compiles CIR to native code via DevEvaluator, -/// and executes via the RocCall ABI. -fn runViaDev( - gpa: std.mem.Allocator, - platform_env: *ModuleEnv, - all_module_envs: []*ModuleEnv, - app_module_env: ?*ModuleEnv, - entrypoint_expr: can.CIR.Expr.Idx, - roc_ops: *echo_platform.host_abi.RocOps, - args_ptr: ?*anyopaque, - result_ptr: *anyopaque, -) !void { - const types = @import("types"); - const DevEvaluator = eval.DevEvaluator; - const ExecutableMemory = eval.ExecutableMemory; - - var dev_eval = DevEvaluator.init(gpa, null) catch { - return error.DevEvaluatorFailed; - }; - defer dev_eval.deinit(); - - // Resolve entrypoint layouts from the CIR expression's type - const layout_store_ptr = try dev_eval.ensureGlobalLayoutStore(all_module_envs); - const module_idx: u32 = for (all_module_envs, 0..) |env, i| { - if (env == platform_env) break @intCast(i); - } else return error.DevEvaluatorFailed; - - const expr_type_var = ModuleEnv.varFrom(entrypoint_expr); - const resolved_type = platform_env.types.resolveVar(expr_type_var); - const maybe_func = resolved_type.desc.content.unwrapFunc(); - - var arg_layouts_buf: [16]layout.Idx = undefined; - var arg_layouts_len: usize = 0; - var ret_layout: layout.Idx = undefined; - - if (maybe_func) |func| { - const arg_vars = platform_env.types.sliceVars(func.args); - var type_scope = types.TypeScope.init(gpa); - defer type_scope.deinit(); - for (arg_vars, 0..) |arg_var, i| { - arg_layouts_buf[i] = layout_store_ptr.fromTypeVar(module_idx, arg_var, &type_scope, null) catch return error.DevEvaluatorFailed; - } - arg_layouts_len = arg_vars.len; - ret_layout = layout_store_ptr.fromTypeVar(module_idx, func.ret, &type_scope, null) catch return error.DevEvaluatorFailed; - } else { - var type_scope = types.TypeScope.init(gpa); - defer type_scope.deinit(); - ret_layout = layout_store_ptr.fromTypeVar(module_idx, expr_type_var, &type_scope, null) catch return error.DevEvaluatorFailed; - } - - const arg_layouts: []const layout.Idx = arg_layouts_buf[0..arg_layouts_len]; - - // Generate native code using the RocCall ABI entrypoint wrapper - var code_result = dev_eval.generateEntrypointCode( - platform_env, - entrypoint_expr, - all_module_envs, - app_module_env, - arg_layouts, - ret_layout, - ) catch { - return error.DevEvaluatorFailed; - }; - defer code_result.deinit(); - - if (code_result.code.len == 0) { - return error.DevEvaluatorFailed; - } - - // Make the generated code executable and run it - var executable = ExecutableMemory.initWithEntryOffset(code_result.code, code_result.entry_offset) catch { - return error.DevEvaluatorFailed; - }; - defer executable.deinit(); - - // Use the DevEvaluator's RocOps (with setjmp/longjmp crash protection) - // so roc_crashed returns an error rather than calling std.process.exit(1). - dev_eval.roc_ops.hosted_fns = roc_ops.hosted_fns; - - dev_eval.callRocABIWithCrashProtection(&executable, result_ptr, args_ptr) catch |err| switch (err) { - error.RocCrashed => return error.DevEvaluatorFailed, - error.Segfault => return error.DevEvaluatorFailed, - }; -} - /// Reads, parses, formats, and overwrites all Roc files at the given paths. /// Recurses into directories to search for Roc files. fn rocFormat(ctx: *CliContext, args: cli_args.FormatArgs) !void { diff --git a/src/compile/runner.zig b/src/compile/runner.zig index 8e1a4a49f48..def69ea6b0c 100644 --- a/src/compile/runner.zig +++ b/src/compile/runner.zig @@ -1,5 +1,6 @@ -//! High-level helpers for running compiled Roc apps through the interpreter. -//! This avoids each call site needing to know about ImportMapping, interpreter init, etc. +//! High-level helpers for running compiled Roc apps. +//! Provides a unified `run` entry point that dispatches to the correct evaluator +//! (interpreter, native JIT via dev backend, or WASM bytecode generation). const std = @import("std"); const can = @import("can"); @@ -7,12 +8,85 @@ const eval = @import("eval"); const roc_target = @import("roc_target"); const builtins = @import("builtins"); +const layout = @import("layout"); const ModuleEnv = can.ModuleEnv; const Interpreter = eval.Interpreter; const BuiltinModules = eval.BuiltinModules; +const DevEvaluator = eval.DevEvaluator; +const ExecutableMemory = eval.ExecutableMemory; +const WasmEvaluator = eval.WasmEvaluator; const RocOps = builtins.host_abi.RocOps; const import_mapping_mod = @import("types").import_mapping; +/// Backend selection enum (re-exported so callers only need `compile`). +pub const EvalBackend = eval.EvalBackend; + +/// Outcome of `run`. +/// - `.executed` — interpreter or native JIT ran; result written to `result_ptr`. +/// - `.wasm` — WASM bytes produced; caller must call `.deinit()` when done. +pub const RunResult = union(enum) { + executed: void, + wasm: eval.WasmCodeResult, +}; + +/// Unified entry point for all evaluation backends. +/// +/// Dispatches to the interpreter, native JIT (dev/llvm), or WASM code generator +/// depending on `backend`. `builtin_modules` and `target` are only consumed by +/// the interpreter path; the other paths accept them but ignore them so callers +/// never need conditional logic. +pub fn run( + backend: EvalBackend, + gpa: std.mem.Allocator, + platform_env: *ModuleEnv, + builtin_modules: *const BuiltinModules, + all_module_envs: []*ModuleEnv, + app_module_env: ?*ModuleEnv, + entrypoint_expr: can.CIR.Expr.Idx, + roc_ops: *RocOps, + args_ptr: ?*anyopaque, + result_ptr: *anyopaque, + target: roc_target.RocTarget, +) !RunResult { + switch (backend) { + .interpreter => { + try runViaInterpreter( + gpa, + platform_env, + builtin_modules, + all_module_envs, + app_module_env, + entrypoint_expr, + roc_ops, + args_ptr, + result_ptr, + target, + ); + return .executed; + }, + .dev, .llvm => { + if (comptime ExecutableMemory == void) return error.UnsupportedOnFreestanding; + try runViaDev( + gpa, + platform_env, + all_module_envs, + app_module_env, + entrypoint_expr, + roc_ops, + args_ptr, + result_ptr, + ); + return .executed; + }, + .wasm => { + var wasm_eval = WasmEvaluator.init(gpa) catch return error.WasmEvaluatorFailed; + const all_module_envs_const: []const *ModuleEnv = all_module_envs; + const wasm_result = wasm_eval.generateWasm(platform_env, entrypoint_expr, all_module_envs_const, app_module_env) catch return error.WasmEvaluatorFailed; + return .{ .wasm = wasm_result }; + }, + } +} + /// Run a compiled Roc entrypoint expression through the interpreter. /// /// This encapsulates interpreter initialization, for-clause type mapping setup, @@ -26,7 +100,7 @@ pub fn runViaInterpreter( app_module_env: ?*ModuleEnv, entrypoint_expr: can.CIR.Expr.Idx, roc_ops: *RocOps, - args_ptr: *anyopaque, + args_ptr: ?*anyopaque, result_ptr: *anyopaque, target: roc_target.RocTarget, ) !void { @@ -65,3 +139,88 @@ pub fn runViaInterpreter( return error.InterpreterFailed; }; } + +/// Run a compiled Roc entrypoint through the dev backend (native code generation). +/// Resolves entrypoint layouts, JIT-compiles CIR to native code via DevEvaluator, +/// and executes via the RocCall ABI. +fn runViaDev( + gpa: std.mem.Allocator, + platform_env: *ModuleEnv, + all_module_envs: []*ModuleEnv, + app_module_env: ?*ModuleEnv, + entrypoint_expr: can.CIR.Expr.Idx, + roc_ops: *RocOps, + args_ptr: ?*anyopaque, + result_ptr: *anyopaque, +) !void { + const types = @import("types"); + + var dev_eval = DevEvaluator.init(gpa, null) catch { + return error.DevEvaluatorFailed; + }; + defer dev_eval.deinit(); + + // Resolve entrypoint layouts from the CIR expression's type + const layout_store_ptr = try dev_eval.ensureGlobalLayoutStore(all_module_envs); + const module_idx: u32 = for (all_module_envs, 0..) |env, i| { + if (env == platform_env) break @intCast(i); + } else return error.DevEvaluatorFailed; + + const expr_type_var = ModuleEnv.varFrom(entrypoint_expr); + const resolved_type = platform_env.types.resolveVar(expr_type_var); + const maybe_func = resolved_type.desc.content.unwrapFunc(); + + var arg_layouts_buf: [16]layout.Idx = undefined; + var arg_layouts_len: usize = 0; + var ret_layout: layout.Idx = undefined; + + if (maybe_func) |func| { + const arg_vars = platform_env.types.sliceVars(func.args); + var type_scope = types.TypeScope.init(gpa); + defer type_scope.deinit(); + for (arg_vars, 0..) |arg_var, i| { + arg_layouts_buf[i] = layout_store_ptr.fromTypeVar(module_idx, arg_var, &type_scope, null) catch return error.DevEvaluatorFailed; + } + arg_layouts_len = arg_vars.len; + ret_layout = layout_store_ptr.fromTypeVar(module_idx, func.ret, &type_scope, null) catch return error.DevEvaluatorFailed; + } else { + var type_scope = types.TypeScope.init(gpa); + defer type_scope.deinit(); + ret_layout = layout_store_ptr.fromTypeVar(module_idx, expr_type_var, &type_scope, null) catch return error.DevEvaluatorFailed; + } + + const arg_layouts: []const layout.Idx = arg_layouts_buf[0..arg_layouts_len]; + + // Generate native code using the RocCall ABI entrypoint wrapper + var code_result = dev_eval.generateEntrypointCode( + platform_env, + entrypoint_expr, + all_module_envs, + app_module_env, + arg_layouts, + ret_layout, + ) catch { + return error.DevEvaluatorFailed; + }; + defer code_result.deinit(); + + if (code_result.code.len == 0) { + return error.DevEvaluatorFailed; + } + + // Make the generated code executable and run it + var executable = ExecutableMemory.initWithEntryOffset(code_result.code, code_result.entry_offset) catch { + return error.DevEvaluatorFailed; + }; + defer executable.deinit(); + + // Use the DevEvaluator's RocOps (with setjmp/longjmp crash protection) + // so roc_crashed returns an error rather than calling std.process.exit(1). + dev_eval.roc_ops.env = roc_ops.env; + dev_eval.roc_ops.hosted_fns = roc_ops.hosted_fns; + + dev_eval.callRocABIWithCrashProtection(&executable, result_ptr, args_ptr) catch |err| switch (err) { + error.RocCrashed => return error.DevEvaluatorFailed, + error.Segfault => return error.DevEvaluatorFailed, + }; +} diff --git a/src/eval/mod.zig b/src/eval/mod.zig index 0a7f7ca25d4..d2bdac9c73f 100644 --- a/src/eval/mod.zig +++ b/src/eval/mod.zig @@ -46,6 +46,9 @@ pub const TestRunner = @import("test_runner.zig").TestRunner; /// WebAssembly-based evaluator for wasm code generation const wasm_evaluator_mod = @import("wasm_evaluator.zig"); pub const WasmEvaluator = wasm_evaluator_mod.WasmEvaluator; +pub const WasmCodeResult = wasm_evaluator_mod.WasmCodeResult; +/// Backend selection enum (re-exported for compile module use without adding a new dep) +pub const EvalBackend = backend.EvalBackend; test "eval tests" { std.testing.refAllDecls(@This()); diff --git a/src/eval/test/helpers.zig b/src/eval/test/helpers.zig index 72f2dfaf799..70b089687e7 100644 --- a/src/eval/test/helpers.zig +++ b/src/eval/test/helpers.zig @@ -588,7 +588,7 @@ fn wasmEvaluatorStr(allocator: std.mem.Allocator, module_env: *ModuleEnv, expr_i // Keep module order aligned with resolveImports/getResolvedModule indices. const all_module_envs = [_]*ModuleEnv{ @constCast(builtin_module_env), module_env }; - var wasm_result = wasm_eval.generateWasm(module_env, expr_idx, &all_module_envs) catch { + var wasm_result = wasm_eval.generateWasm(module_env, expr_idx, &all_module_envs, null) catch { return error.WasmGenerateCodeFailed; }; defer wasm_result.deinit(); diff --git a/src/eval/wasm_evaluator.zig b/src/eval/wasm_evaluator.zig index a159fed00ab..e9b6595a2a1 100644 --- a/src/eval/wasm_evaluator.zig +++ b/src/eval/wasm_evaluator.zig @@ -187,6 +187,7 @@ pub const WasmEvaluator = struct { module_env: *ModuleEnv, expr_idx: CIR.Expr.Idx, all_module_envs: []const *ModuleEnv, + app_module_env: ?*ModuleEnv, ) Error!WasmCodeResult { // Other evaluators may have resolved this module's imports against a // different module ordering. Refresh them here so CIR external lookups @@ -202,6 +203,17 @@ pub const WasmEvaluator = struct { } } + // Find app module index (needed to resolve platform `requires` clauses) + var app_module_idx: ?u32 = null; + if (app_module_env) |app_env| { + for (all_module_envs, 0..) |env, i| { + if (env == app_env) { + app_module_idx = @intCast(i); + break; + } + } + } + // Get layout store (wasm32 target) const layout_store_ptr = try self.ensureGlobalLayoutStore(all_module_envs); layout_store_ptr.setModuleEnvs(all_module_envs); @@ -221,7 +233,7 @@ pub const WasmEvaluator = struct { all_module_envs, &module_env.types, module_idx, - null, // app_module_idx - not used for Wasm evaluation + app_module_idx, ) catch return error.OutOfMemory; defer mir_lower.deinit(); diff --git a/src/glue/glue.zig b/src/glue/glue.zig index 92f408959a5..8c99e51cd0d 100644 --- a/src/glue/glue.zig +++ b/src/glue/glue.zig @@ -416,40 +416,22 @@ fn rocGlueInner(gpa: Allocator, stderr: *std.Io.Writer, stdout: *std.Io.Writer, // 7. Run glue spec via selected backend var result_buf: ResultListFileStr = undefined; - switch (args.backend) { - .dev, .llvm => { - runViaDev( - gpa, - entry.platform_env, - resolved.all_module_envs, - entry.app_module_env, - entry.entrypoint_expr, - &roc_ops, - @ptrCast(&types_list), - @ptrCast(&result_buf), - ) catch |err| { - stderr.print("Dev backend error running glue spec: {}\n", .{err}) catch {}; - return error.CompilationFailed; - }; - }, - .interpreter => { - compile.runner.runViaInterpreter( - gpa, - entry.platform_env, - glue_build_env.builtin_modules, - resolved.all_module_envs, - entry.app_module_env, - entry.entrypoint_expr, - &roc_ops, - @ptrCast(&types_list), - @ptrCast(&result_buf), - RocTarget.detectNative(), - ) catch |err| { - stderr.print("Interpreter error running glue spec: {}\n", .{err}) catch {}; - return error.CompilationFailed; - }; - }, - } + _ = compile.runner.run( + args.backend, + gpa, + entry.platform_env, + glue_build_env.builtin_modules, + resolved.all_module_envs, + entry.app_module_env, + entry.entrypoint_expr, + &roc_ops, + @ptrCast(&types_list), + @ptrCast(&result_buf), + RocTarget.detectNative(), + ) catch |err| { + stderr.print("Backend error running glue spec: {}\n", .{err}) catch {}; + return error.CompilationFailed; + }; // 8. Extract Try(List(File), Str) and write files const glue_result = extractGlueResult(&result_buf); @@ -2340,92 +2322,3 @@ fn generateStubExprFromTypeAnno(gpa: std.mem.Allocator, env: *ModuleEnv, ast: *c }, } } - -/// Run a compiled Roc entrypoint through the dev backend (native code generation). -fn runViaDev( - gpa: Allocator, - platform_env: *ModuleEnv, - all_module_envs: []*ModuleEnv, - app_module_env: ?*ModuleEnv, - entrypoint_expr: can.CIR.Expr.Idx, - roc_ops: *builtins.host_abi.RocOps, - args_ptr: ?*anyopaque, - result_ptr: *anyopaque, -) !void { - const eval_mod = @import("eval"); - const types_mod = @import("types"); - const DevEvaluator = eval_mod.DevEvaluator; - const ExecutableMemory = eval_mod.ExecutableMemory; - - var dev_eval = DevEvaluator.init(gpa, null) catch { - return error.CompilationFailed; - }; - defer dev_eval.deinit(); - - // Resolve entrypoint layouts from the CIR expression's type - const layout_store_ptr = dev_eval.ensureGlobalLayoutStore(all_module_envs) catch return error.CompilationFailed; - const module_idx: u32 = for (all_module_envs, 0..) |env, i| { - if (env == platform_env) break @intCast(i); - } else return error.CompilationFailed; - - const expr_type_var = ModuleEnv.varFrom(entrypoint_expr); - const resolved_type = platform_env.types.resolveVar(expr_type_var); - const maybe_func = resolved_type.desc.content.unwrapFunc(); - - var arg_layouts_buf: [16]layout.Idx = undefined; - var arg_layouts_len: usize = 0; - var ret_layout: layout.Idx = undefined; - - if (maybe_func) |func| { - const arg_vars = platform_env.types.sliceVars(func.args); - var type_scope = types_mod.TypeScope.init(gpa); - defer type_scope.deinit(); - for (arg_vars, 0..) |arg_var, i| { - arg_layouts_buf[i] = layout_store_ptr.fromTypeVar(module_idx, arg_var, &type_scope, null) catch return error.CompilationFailed; - } - arg_layouts_len = arg_vars.len; - ret_layout = layout_store_ptr.fromTypeVar(module_idx, func.ret, &type_scope, null) catch return error.CompilationFailed; - } else { - var type_scope = types_mod.TypeScope.init(gpa); - defer type_scope.deinit(); - ret_layout = layout_store_ptr.fromTypeVar(module_idx, expr_type_var, &type_scope, null) catch return error.CompilationFailed; - } - - const arg_layouts: []const layout.Idx = arg_layouts_buf[0..arg_layouts_len]; - - var code_result = dev_eval.generateEntrypointCode( - platform_env, - entrypoint_expr, - all_module_envs, - app_module_env, - arg_layouts, - ret_layout, - ) catch { - return error.CompilationFailed; - }; - defer code_result.deinit(); - - if (code_result.code.len == 0) { - return error.CompilationFailed; - } - - var executable = ExecutableMemory.initWithEntryOffset(code_result.code, code_result.entry_offset) catch { - return error.CompilationFailed; - }; - defer executable.deinit(); - - // Use the DevEvaluator's RocOps (which has setjmp/longjmp crash protection) - // instead of the caller's RocOps, so roc_crashed returns an error rather - // than calling std.process.exit(1). - // Splice in the caller's hosted functions so the generated code can call them. - dev_eval.roc_ops.hosted_fns = roc_ops.hosted_fns; - - dev_eval.callRocABIWithCrashProtection(&executable, result_ptr, args_ptr) catch |err| switch (err) { - error.RocCrashed => { - return error.CompilationFailed; - }, - error.Segfault => { - return error.CompilationFailed; - }, - }; -}