diff --git a/Cargo.lock b/Cargo.lock index 376f11b804..885b0f24b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4009,6 +4009,7 @@ dependencies = [ "roc_std", "roc_target", "roc_types", + "signal-hook", "strip-ansi-escapes", "target-lexicon", "tempfile", diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 199720e31d..ab7a7e167d 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -8,12 +8,10 @@ use roc_build::link::{LinkType, LinkingStrategy}; use roc_collections::VecMap; use roc_error_macros::{internal_error, user_error}; use roc_gen_llvm::llvm::build::LlvmBackendMode; -use roc_gen_llvm::run_roc::RocCallResult; -use roc_gen_llvm::run_roc_dylib; use roc_load::{ExecutionMode, Expectations, LoadConfig, LoadingProblem, Threading}; use roc_module::symbol::{Interns, ModuleId}; use roc_mono::ir::OptLevel; -use roc_repl_expect::run::{expect_mono_module_to_dylib, roc_dev_expect}; +use roc_repl_expect::run::expect_mono_module_to_dylib; use roc_target::TargetInfo; use std::env; use std::ffi::{CString, OsStr}; @@ -320,8 +318,6 @@ pub enum FormatMode { CheckOnly, } -const SHM_SIZE: i64 = 1024; - pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result { let start_time = Instant::now(); let arena = Bump::new(); @@ -407,13 +403,6 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result { let mut writer = std::io::stdout(); - let mut shared_buffer = vec![0u8; SHM_SIZE as usize]; - - let set_shared_buffer = run_roc_dylib!(lib, "set_shared_buffer", (*mut u8, usize), ()); - let mut result = RocCallResult::default(); - let slice = (shared_buffer.as_mut_ptr(), shared_buffer.len()); - unsafe { set_shared_buffer(slice, &mut result) }; - let (failed, passed) = roc_repl_expect::run::run_expects( &mut writer, roc_reporting::report::RenderTarget::ColorTerminal, @@ -421,7 +410,6 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result { interns, &lib, &mut expectations, - shared_buffer.as_mut_ptr(), expects, ) .unwrap(); @@ -953,94 +941,13 @@ impl ExecutableFile { // with Expect #[cfg(target_family = "unix")] unsafe fn roc_run_native_debug( - executable: ExecutableFile, - argv: &[*const c_char], - envp: &[*const c_char], - mut expectations: VecMap, - interns: Interns, + _executable: ExecutableFile, + _argv: &[*const c_char], + _envp: &[*const c_char], + _expectations: VecMap, + _interns: Interns, ) { - use signal_hook::{consts::signal::SIGCHLD, consts::signal::SIGUSR1, iterator::Signals}; - - let mut signals = Signals::new(&[SIGCHLD, SIGUSR1]).unwrap(); - - match libc::fork() { - 0 => { - // we are the child - - if executable.execve(argv, envp) < 0 { - // Get the current value of errno - let e = errno::errno(); - - // Extract the error code as an i32 - let code = e.0; - - // Display a human-friendly error message - println!("💥 Error {}: {}", code, e); - } - } - -1 => { - // something failed - - // Get the current value of errno - let e = errno::errno(); - - // Extract the error code as an i32 - let code = e.0; - - // Display a human-friendly error message - println!("Error {}: {}", code, e); - - process::exit(1) - } - 1.. => { - let name = "/roc_expect_buffer"; // IMPORTANT: shared memory object names must begin with / and contain no other slashes! - let cstring = CString::new(name).unwrap(); - - let arena = &bumpalo::Bump::new(); - let interns = arena.alloc(interns); - - for sig in &mut signals { - match sig { - SIGCHLD => { - // clean up - libc::shm_unlink(cstring.as_ptr().cast()); - - // done! - process::exit(0); - } - SIGUSR1 => { - // this is the signal we use for an expect failure. Let's see what the child told us - let shared_fd = - libc::shm_open(cstring.as_ptr().cast(), libc::O_RDONLY, 0o666); - - libc::ftruncate(shared_fd, SHM_SIZE); - - let shared_ptr = libc::mmap( - std::ptr::null_mut(), - SHM_SIZE as usize, - libc::PROT_READ, - libc::MAP_SHARED, - shared_fd, - 0, - ); - - let shared_memory_ptr: *mut u8 = shared_ptr.cast(); - - roc_dev_expect( - &mut std::io::stdout(), - arena, - &mut expectations, - interns, - shared_memory_ptr, - ) - .unwrap(); - } - _ => println!("received signal {}", sig), - } - } - } - _ => unreachable!(), - } + todo!() } #[cfg(target_os = "linux")] diff --git a/crates/compiler/alias_analysis/src/lib.rs b/crates/compiler/alias_analysis/src/lib.rs index d01e178d2e..c177c47fa8 100644 --- a/crates/compiler/alias_analysis/src/lib.rs +++ b/crates/compiler/alias_analysis/src/lib.rs @@ -463,6 +463,7 @@ fn stmt_spec<'a>( builder.add_choice(block, &cases) } Expect { remainder, .. } => stmt_spec(builder, env, block, layout, remainder), + ExpectFx { remainder, .. } => stmt_spec(builder, env, block, layout, remainder), Ret(symbol) => Ok(env.symbols[symbol]), Refcounting(modify_rc, continuation) => match modify_rc { ModifyRc::Inc(symbol, _) => { diff --git a/crates/compiler/builtins/bitcode/src/expect.zig b/crates/compiler/builtins/bitcode/src/expect.zig index eeccf5d952..64bc0ef748 100644 --- a/crates/compiler/builtins/bitcode/src/expect.zig +++ b/crates/compiler/builtins/bitcode/src/expect.zig @@ -1,10 +1,5 @@ const std = @import("std"); -extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int; -extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque; -extern fn kill(pid: c_int, sig: c_int) c_int; -extern fn getppid() c_int; - const SIGUSR1: c_int = 10; const O_RDWR: c_int = 2; @@ -26,9 +21,3 @@ pub fn setSharedBuffer(ptr: [*]u8, length: usize) callconv(.C) usize { pub fn expectFailedStart() callconv(.C) [*]u8 { return SHARED_BUFFER.ptr; } - -pub fn expectFailedFinalize() callconv(.C) void { - const parent_pid = getppid(); - - _ = kill(parent_pid, SIGUSR1); -} diff --git a/crates/compiler/builtins/bitcode/src/main.zig b/crates/compiler/builtins/bitcode/src/main.zig index 647bcf94fe..bbd850698e 100644 --- a/crates/compiler/builtins/bitcode/src/main.zig +++ b/crates/compiler/builtins/bitcode/src/main.zig @@ -168,7 +168,6 @@ comptime { if (builtin.target.cpu.arch != .wasm32) { exportUtilsFn(expect.expectFailedStart, "expect_failed_start"); - exportUtilsFn(expect.expectFailedFinalize, "expect_failed_finalize"); // sets the buffer used for expect failures @export(expect.setSharedBuffer, .{ .name = "set_shared_buffer", .linkage = .Weak }); diff --git a/crates/compiler/can/src/copy.rs b/crates/compiler/can/src/copy.rs index 46a88e1073..5722dcaf27 100644 --- a/crates/compiler/can/src/copy.rs +++ b/crates/compiler/can/src/copy.rs @@ -613,6 +613,16 @@ fn deep_copy_expr_help(env: &mut C, copied: &mut Vec, expr lookups_in_cond: lookups_in_cond.to_vec(), }, + ExpectFx { + loc_condition, + loc_continuation, + lookups_in_cond, + } => ExpectFx { + loc_condition: Box::new(loc_condition.map(|e| go_help!(e))), + loc_continuation: Box::new(loc_continuation.map(|e| go_help!(e))), + lookups_in_cond: lookups_in_cond.to_vec(), + }, + TypedHole(v) => TypedHole(sub!(*v)), RuntimeError(err) => RuntimeError(err.clone()), diff --git a/crates/compiler/can/src/expr.rs b/crates/compiler/can/src/expr.rs index 7f68f20a94..65bfe3f083 100644 --- a/crates/compiler/can/src/expr.rs +++ b/crates/compiler/can/src/expr.rs @@ -231,6 +231,13 @@ pub enum Expr { lookups_in_cond: Vec<(Symbol, Variable)>, }, + // not parsed, but is generated when lowering toplevel effectful expects + ExpectFx { + loc_condition: Box>, + loc_continuation: Box>, + lookups_in_cond: Vec<(Symbol, Variable)>, + }, + /// Rendered as empty box in editor TypedHole(Variable), @@ -277,6 +284,7 @@ impl Expr { Category::OpaqueWrap(opaque_name) } Self::Expect { .. } => Category::Expect, + Self::ExpectFx { .. } => Category::Expect, // these nodes place no constraints on the expression's type Self::TypedHole(_) | Self::RuntimeError(..) => Category::Unknown, @@ -1772,6 +1780,28 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> } } + ExpectFx { + loc_condition, + loc_continuation, + lookups_in_cond, + } => { + let loc_condition = Loc { + region: loc_condition.region, + value: inline_calls(var_store, scope, loc_condition.value), + }; + + let loc_continuation = Loc { + region: loc_continuation.region, + value: inline_calls(var_store, scope, loc_continuation.value), + }; + + ExpectFx { + loc_condition: Box::new(loc_condition), + loc_continuation: Box::new(loc_continuation), + lookups_in_cond, + } + } + LetRec(defs, loc_expr, mark) => { let mut new_defs = Vec::with_capacity(defs.len()); @@ -2327,7 +2357,7 @@ impl Declarations { ) -> usize { let index = self.declarations.len(); - self.declarations.push(DeclarationTag::Expectation); + self.declarations.push(DeclarationTag::ExpectationFx); self.variables.push(Variable::BOOL); self.symbols.push(Loc::at(preceding_comment, name)); self.annotations.push(None); @@ -2513,13 +2543,13 @@ impl Declarations { } Expectation => { let loc_expr = - toplevel_expect_to_inline_expect(self.expressions[index].clone()); + toplevel_expect_to_inline_expect_pure(self.expressions[index].clone()); collector.visit_expr(&loc_expr.value, loc_expr.region, var); } ExpectationFx => { let loc_expr = - toplevel_expect_to_inline_expect(self.expressions[index].clone()); + toplevel_expect_to_inline_expect_fx(self.expressions[index].clone()); collector.visit_expr(&loc_expr.value, loc_expr.region, var); } @@ -2658,6 +2688,9 @@ fn get_lookup_symbols(expr: &Expr, var_store: &mut VarStore) -> Vec<(Symbol, Var } Expr::Expect { loc_continuation, .. + } + | Expr::ExpectFx { + loc_continuation, .. } => { stack.push(&(*loc_continuation).value); @@ -2705,7 +2738,15 @@ fn get_lookup_symbols(expr: &Expr, var_store: &mut VarStore) -> Vec<(Symbol, Var /// This is supposed to happen just before monomorphization: /// all type errors and such are generated from the user source, /// but this transformation means that we don't need special codegen for toplevel expects -pub fn toplevel_expect_to_inline_expect(mut loc_expr: Loc) -> Loc { +pub fn toplevel_expect_to_inline_expect_pure(loc_expr: Loc) -> Loc { + toplevel_expect_to_inline_expect_help(loc_expr, false) +} + +pub fn toplevel_expect_to_inline_expect_fx(loc_expr: Loc) -> Loc { + toplevel_expect_to_inline_expect_help(loc_expr, true) +} + +fn toplevel_expect_to_inline_expect_help(mut loc_expr: Loc, has_effects: bool) -> Loc { enum StoredDef { NonRecursive(Region, Box), Recursive(Region, Vec, IllegalCycleMark), @@ -2735,10 +2776,18 @@ pub fn toplevel_expect_to_inline_expect(mut loc_expr: Loc) -> Loc { } let expect_region = loc_expr.region; - let expect = Expr::Expect { - loc_condition: Box::new(loc_expr), - loc_continuation: Box::new(Loc::at_zero(Expr::EmptyRecord)), - lookups_in_cond, + let expect = if has_effects { + Expr::ExpectFx { + loc_condition: Box::new(loc_expr), + loc_continuation: Box::new(Loc::at_zero(Expr::EmptyRecord)), + lookups_in_cond, + } + } else { + Expr::Expect { + loc_condition: Box::new(loc_expr), + loc_continuation: Box::new(Loc::at_zero(Expr::EmptyRecord)), + lookups_in_cond, + } }; let mut loc_expr = Loc::at(expect_region, expect); @@ -2764,14 +2813,21 @@ struct ExpectCollector { impl crate::traverse::Visitor for ExpectCollector { fn visit_expr(&mut self, expr: &Expr, _region: Region, var: Variable) { - if let Expr::Expect { - lookups_in_cond, - loc_condition, - .. - } = expr - { - self.expects - .insert(loc_condition.region, lookups_in_cond.to_vec()); + match expr { + Expr::Expect { + lookups_in_cond, + loc_condition, + .. + } + | Expr::ExpectFx { + lookups_in_cond, + loc_condition, + .. + } => { + self.expects + .insert(loc_condition.region, lookups_in_cond.to_vec()); + } + _ => (), } walk_expr(self, expr, var) diff --git a/crates/compiler/can/src/module.rs b/crates/compiler/can/src/module.rs index 736f975488..aa5a450dba 100644 --- a/crates/compiler/can/src/module.rs +++ b/crates/compiler/can/src/module.rs @@ -964,6 +964,23 @@ fn fix_values_captured_in_closure_expr( ); } + ExpectFx { + loc_condition, + loc_continuation, + lookups_in_cond: _, + } => { + fix_values_captured_in_closure_expr( + &mut loc_condition.value, + no_capture_symbols, + closure_captures, + ); + fix_values_captured_in_closure_expr( + &mut loc_continuation.value, + no_capture_symbols, + closure_captures, + ); + } + Closure(ClosureData { captured_symbols, name, diff --git a/crates/compiler/can/src/traverse.rs b/crates/compiler/can/src/traverse.rs index 7fb0b7c86c..6795923cec 100644 --- a/crates/compiler/can/src/traverse.rs +++ b/crates/compiler/can/src/traverse.rs @@ -276,6 +276,19 @@ pub fn walk_expr(visitor: &mut V, expr: &Expr, var: Variable) { Variable::NULL, ); } + Expr::ExpectFx { + loc_condition, + loc_continuation, + lookups_in_cond: _, + } => { + // TODO: what type does an expect have? bool + visitor.visit_expr(&loc_condition.value, loc_condition.region, Variable::NULL); + visitor.visit_expr( + &loc_continuation.value, + loc_continuation.region, + Variable::NULL, + ); + } Expr::TypedHole(_) => { /* terminal */ } Expr::RuntimeError(..) => { /* terminal */ } } diff --git a/crates/compiler/constrain/src/expr.rs b/crates/compiler/constrain/src/expr.rs index ffe4880bc3..1fec4018c0 100644 --- a/crates/compiler/constrain/src/expr.rs +++ b/crates/compiler/constrain/src/expr.rs @@ -520,6 +520,53 @@ pub fn constrain_expr( constraints.exists_many(vars, all_constraints) } + ExpectFx { + loc_condition, + loc_continuation, + lookups_in_cond, + } => { + let expect_bool = |region| { + let bool_type = Type::Variable(Variable::BOOL); + Expected::ForReason(Reason::ExpectCondition, bool_type, region) + }; + + let cond_con = constrain_expr( + constraints, + env, + loc_condition.region, + &loc_condition.value, + expect_bool(loc_condition.region), + ); + + let continuation_con = constrain_expr( + constraints, + env, + loc_continuation.region, + &loc_continuation.value, + expected, + ); + + // + 2 for cond_con and continuation_con + let mut all_constraints = Vec::with_capacity(lookups_in_cond.len() + 2); + + all_constraints.push(cond_con); + all_constraints.push(continuation_con); + + let mut vars = Vec::with_capacity(lookups_in_cond.len()); + + for (symbol, var) in lookups_in_cond.iter() { + vars.push(*var); + + all_constraints.push(constraints.lookup( + *symbol, + NoExpectation(Type::Variable(*var)), + Region::zero(), + )); + } + + constraints.exists_many(vars, all_constraints) + } + If { cond_var, branch_var, diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index ba7c272994..0fbe0fa115 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -1004,7 +1004,8 @@ trait Backend<'a> { } } - Stmt::Expect { .. } => todo!("expect is not implemented in the wasm backend"), + Stmt::Expect { .. } => todo!("expect is not implemented in the dev backend"), + Stmt::ExpectFx { .. } => todo!("expect-fx is not implemented in the dev backend"), Stmt::RuntimeError(_) => {} } diff --git a/crates/compiler/gen_llvm/src/llvm/build.rs b/crates/compiler/gen_llvm/src/llvm/build.rs index bea0f9c84c..66958fe4f2 100644 --- a/crates/compiler/gen_llvm/src/llvm/build.rs +++ b/crates/compiler/gen_llvm/src/llvm/build.rs @@ -199,10 +199,6 @@ impl LlvmBackendMode { LlvmBackendMode::CliTest => true, } } - - fn runs_expects_in_separate_process(self) -> bool { - false - } } pub struct Env<'a, 'ctx, 'env> { @@ -2802,15 +2798,67 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( lookups, ); - // NOTE: signals to the parent process that an expect failed - if env.mode.runs_expects_in_separate_process() { - let func = env - .module - .get_function(bitcode::UTILS_EXPECT_FAILED_FINALIZE) - .unwrap(); + bd.build_unconditional_branch(then_block); + } + roc_target::PtrWidth::Bytes4 => { + // temporary WASM implementation + throw_exception(env, "An expectation failed!"); + } + } + } else { + bd.position_at_end(throw_block); + bd.build_unconditional_branch(then_block); + } - bd.build_call(func, &[], "call_expect_finalize_failed"); - } + bd.position_at_end(then_block); + + build_exp_stmt( + env, + layout_ids, + func_spec_solutions, + scope, + parent, + remainder, + ) + } + + ExpectFx { + condition: cond_symbol, + region, + lookups, + layouts: _, + remainder, + } => { + let bd = env.builder; + let context = env.context; + + let (cond, _cond_layout) = load_symbol_and_layout(scope, cond_symbol); + + let condition = bd.build_int_compare( + IntPredicate::EQ, + cond.into_int_value(), + context.bool_type().const_int(1, false), + "is_true", + ); + + let then_block = context.append_basic_block(parent, "then_block"); + let throw_block = context.append_basic_block(parent, "throw_block"); + + bd.build_conditional_branch(condition, then_block, throw_block); + + if env.mode.runs_expects() { + bd.position_at_end(throw_block); + + match env.target_info.ptr_width() { + roc_target::PtrWidth::Bytes8 => { + clone_to_shared_memory( + env, + scope, + layout_ids, + *cond_symbol, + *region, + lookups, + ); bd.build_unconditional_branch(then_block); } diff --git a/crates/compiler/gen_wasm/src/backend.rs b/crates/compiler/gen_wasm/src/backend.rs index 6bf90f5309..79ff53640a 100644 --- a/crates/compiler/gen_wasm/src/backend.rs +++ b/crates/compiler/gen_wasm/src/backend.rs @@ -708,6 +708,7 @@ impl<'a> WasmBackend<'a> { Stmt::Refcounting(modify, following) => self.stmt_refcounting(modify, following), Stmt::Expect { .. } => todo!("expect is not implemented in the wasm backend"), + Stmt::ExpectFx { .. } => todo!("expect-fx is not implemented in the wasm backend"), Stmt::RuntimeError(msg) => self.stmt_runtime_error(msg), } diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs index afb32050a2..b733e0845c 100644 --- a/crates/compiler/load_internal/src/file.rs +++ b/crates/compiler/load_internal/src/file.rs @@ -707,6 +707,12 @@ struct LateSpecializationsModule<'a> { procs_base: ProcsBase<'a>, } +#[derive(Debug, Default)] +pub struct ToplevelExpects { + pub pure: VecMap, + pub fx: VecMap, +} + #[derive(Debug)] pub struct MonomorphizedModule<'a> { pub module_id: ModuleId, @@ -716,7 +722,7 @@ pub struct MonomorphizedModule<'a> { pub can_problems: MutMap>, pub type_problems: MutMap>, pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, - pub toplevel_expects: VecMap, + pub toplevel_expects: ToplevelExpects, pub entry_point: EntryPoint<'a>, pub exposed_to_host: ExposedToHost, pub sources: MutMap)>, @@ -821,7 +827,7 @@ enum Msg<'a> { solved_subs: Solved, module_timing: ModuleTiming, abilities_store: AbilitiesStore, - toplevel_expects: VecMap, + toplevel_expects: ToplevelExpects, }, MadeSpecializations { module_id: ModuleId, @@ -908,7 +914,7 @@ struct State<'a> { pub module_cache: ModuleCache<'a>, pub dependencies: Dependencies<'a>, pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, - pub toplevel_expects: VecMap, + pub toplevel_expects: ToplevelExpects, pub exposed_to_host: ExposedToHost, /// This is the "final" list of IdentIds, after canonicalization and constraint gen @@ -981,7 +987,7 @@ impl<'a> State<'a> { module_cache: ModuleCache::default(), dependencies, procedures: MutMap::default(), - toplevel_expects: VecMap::default(), + toplevel_expects: ToplevelExpects::default(), exposed_to_host: ExposedToHost::default(), exposed_types, arc_modules, @@ -2527,7 +2533,8 @@ fn update<'a>( let subs = solved_subs.into_inner(); - state.toplevel_expects.extend(toplevel_expects); + state.toplevel_expects.pure.extend(toplevel_expects.pure); + state.toplevel_expects.fx.extend(toplevel_expects.fx); state .module_cache @@ -4818,7 +4825,7 @@ fn build_pending_specializations<'a>( let find_specializations_start = Instant::now(); let mut module_thunks = bumpalo::collections::Vec::new_in(arena); - let mut toplevel_expects = VecMap::default(); + let mut toplevel_expects = ToplevelExpects::default(); let mut procs_base = ProcsBase { partial_procs: BumpMap::default(), @@ -5111,7 +5118,7 @@ fn build_pending_specializations<'a>( ); } - let body = roc_can::expr::toplevel_expect_to_inline_expect(body); + let body = roc_can::expr::toplevel_expect_to_inline_expect_pure(body); let proc = PartialProc { annotation: expr_var, @@ -5131,7 +5138,7 @@ fn build_pending_specializations<'a>( let expr_region = declarations.expressions[index].region; let region = Region::span_across(&name_region, &expr_region); - toplevel_expects.insert(symbol, region); + toplevel_expects.pure.insert(symbol, region); procs_base.partial_procs.insert(symbol, proc); } ExpectationFx => { @@ -5187,7 +5194,7 @@ fn build_pending_specializations<'a>( ); } - let body = roc_can::expr::toplevel_expect_to_inline_expect(body); + let body = roc_can::expr::toplevel_expect_to_inline_expect_fx(body); let proc = PartialProc { annotation: expr_var, @@ -5207,7 +5214,7 @@ fn build_pending_specializations<'a>( let expr_region = declarations.expressions[index].region; let region = Region::span_across(&name_region, &expr_region); - toplevel_expects.insert(symbol, region); + toplevel_expects.fx.insert(symbol, region); procs_base.partial_procs.insert(symbol, proc); } } diff --git a/crates/compiler/mono/src/borrow.rs b/crates/compiler/mono/src/borrow.rs index 0bb6e2796a..5aeabd2f11 100644 --- a/crates/compiler/mono/src/borrow.rs +++ b/crates/compiler/mono/src/borrow.rs @@ -309,6 +309,7 @@ impl<'a> ParamMap<'a> { } Expect { remainder, .. } => stack.push(remainder), + ExpectFx { remainder, .. } => stack.push(remainder), Switch { branches, @@ -820,6 +821,10 @@ impl<'a> BorrowInfState<'a> { self.collect_stmt(param_map, remainder); } + ExpectFx { remainder, .. } => { + self.collect_stmt(param_map, remainder); + } + Refcounting(_, _) => unreachable!("these have not been introduced yet"), Ret(_) | RuntimeError(_) => { @@ -988,6 +993,7 @@ fn call_info_stmt<'a>(arena: &'a Bump, stmt: &Stmt<'a>, info: &mut CallInfo<'a>) } Expect { remainder, .. } => stack.push(remainder), + ExpectFx { remainder, .. } => stack.push(remainder), Refcounting(_, _) => unreachable!("these have not been introduced yet"), diff --git a/crates/compiler/mono/src/inc_dec.rs b/crates/compiler/mono/src/inc_dec.rs index 0a06e37f45..603215f5a2 100644 --- a/crates/compiler/mono/src/inc_dec.rs +++ b/crates/compiler/mono/src/inc_dec.rs @@ -119,6 +119,17 @@ pub fn occurring_variables(stmt: &Stmt<'_>) -> (MutSet, MutSet) stack.push(remainder); } + ExpectFx { + condition, + remainder, + lookups, + .. + } => { + result.insert(*condition); + result.extend(lookups.iter().copied()); + stack.push(remainder); + } + Jump(_, arguments) => { result.extend(arguments.iter().copied()); } @@ -671,8 +682,8 @@ impl<'a> Context<'a> { match ownership { DataBorrowedFunctionOwns => { - // the data is borrowed; - // increment it to own the values so the function can use them + // the data is borrowed; increment it to own the values so the function + // can use them let rc = Stmt::Refcounting(ModifyRc::Inc(argument, 1), stmt); stmt = self.arena.alloc(rc); @@ -690,8 +701,8 @@ impl<'a> Context<'a> { }}; } - // incrementing/consuming the closure (if needed) is done by the zig implementation. - // We don't want to touch the RC on the roc side, so treat these as borrowed. + // incrementing/consuming the closure (if needed) is done by the zig implementation. We + // don't want to touch the RC on the roc side, so treat these as borrowed. const FUNCTION: bool = BORROWED; const CLOSURE_DATA: bool = BORROWED; @@ -753,9 +764,9 @@ impl<'a> Context<'a> { handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships) } ListSortWith { xs } => { - // NOTE: we may apply the function to the same argument multiple times. - // for that to be valid, the function must borrow its argument. This is not - // enforced at the moment + // NOTE: we may apply the function to the same argument multiple times. for that to + // be valid, the function must borrow its argument. This is not enforced at the + // moment // // we also don't check that both arguments have the same ownership characteristics let ownerships = [(xs, function_ps[0])]; @@ -768,7 +779,8 @@ impl<'a> Context<'a> { match ownership { DataOwnedFunctionOwns => { - // if non-unique, elements have been consumed, must still consume the list itself + // if non-unique, elements have been consumed, must still consume the + // list itself let rc = Stmt::Refcounting(ModifyRc::DecRef(xs), b); let condition_stmt = branch_on_list_uniqueness( @@ -914,8 +926,7 @@ impl<'a> Context<'a> { } EmptyArray | Literal(_) | Reset { .. } | RuntimeErrorFunction(_) => { - // EmptyArray is always stack-allocated - // function pointers are persistent + // EmptyArray is always stack-allocated function pointers are persistent self.arena.alloc(Stmt::Let(z, v, l, b)) } }; @@ -924,8 +935,7 @@ impl<'a> Context<'a> { } fn update_var_info(&self, symbol: Symbol, layout: &Layout<'a>, expr: &Expr<'a>) -> Self { - // is this value a constant? - // TODO do function pointers also fall into this category? + // is this value a constant? TODO do function pointers also fall into this category? let persistent = false; // must this value be consumed? @@ -979,9 +989,7 @@ impl<'a> Context<'a> { // Add `dec` instructions for parameters that are // - // - references - // - not alive in `b` - // - not borrow. + // - references - not alive in `b` - not borrow. // // That is, we must make sure these parameters are consumed. fn add_dec_for_dead_params( @@ -1021,9 +1029,9 @@ impl<'a> Context<'a> { ) -> (&'a Stmt<'a>, LiveVarSet) { use Stmt::*; - // let-chains can be very long, especially for large (list) literals - // in (rust) debug mode, this function can overflow the stack for such values - // so we have to write an explicit loop. + // let-chains can be very long, especially for large (list) literals in (rust) debug mode, + // this function can overflow the stack for such values so we have to write an explicit + // loop. { let mut cont = stmt; let mut triples = Vec::new_in(self.arena); @@ -1199,6 +1207,30 @@ impl<'a> Context<'a> { (expect, b_live_vars) } + ExpectFx { + remainder, + condition, + region, + lookups, + layouts, + } => { + let (b, mut b_live_vars) = self.visit_stmt(codegen, remainder); + + let expect = self.arena.alloc(Stmt::ExpectFx { + condition: *condition, + region: *region, + lookups, + layouts, + remainder: b, + }); + + let expect = self.add_inc_before_consume_all(lookups, expect, &b_live_vars); + + b_live_vars.extend(lookups.iter().copied()); + + (expect, b_live_vars) + } + RuntimeError(_) | Refcounting(_, _) => (stmt, MutSet::default()), } } @@ -1313,6 +1345,17 @@ pub fn collect_stmt( collect_stmt(remainder, jp_live_vars, vars) } + ExpectFx { + condition, + remainder, + lookups, + .. + } => { + vars.insert(*condition); + vars.extend(lookups.iter().copied()); + collect_stmt(remainder, jp_live_vars, vars) + } + Join { id: j, parameters, diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index 6904d74d77..12fc3c612a 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -1513,6 +1513,14 @@ pub enum Stmt<'a> { /// what happens after the expect remainder: &'a Stmt<'a>, }, + ExpectFx { + condition: Symbol, + region: Region, + lookups: &'a [Symbol], + layouts: &'a [Layout<'a>], + /// what happens after the expect + remainder: &'a Stmt<'a>, + }, /// a join point `join f = in remainder` Join { id: JoinPointId, @@ -2100,6 +2108,17 @@ impl<'a> Stmt<'a> { .append(alloc.hardline()) .append(remainder.to_doc(alloc)), + ExpectFx { + condition, + remainder, + .. + } => alloc + .text("expect-fx ") + .append(symbol_to_doc(alloc, *condition)) + .append(";") + .append(alloc.hardline()) + .append(remainder.to_doc(alloc)), + Ret(symbol) => alloc .text("ret ") .append(symbol_to_doc(alloc, *symbol)) @@ -4163,6 +4182,7 @@ pub fn with_hole<'a>( EmptyRecord => let_empty_struct(assigned, hole), Expect { .. } => unreachable!("I think this is unreachable"), + ExpectFx { .. } => unreachable!("I think this is unreachable"), If { cond_var, @@ -6133,6 +6153,45 @@ pub fn from_can<'a>( stmt } + ExpectFx { + loc_condition, + loc_continuation, + lookups_in_cond, + } => { + let rest = from_can(env, variable, loc_continuation.value, procs, layout_cache); + let cond_symbol = env.unique_symbol(); + + let lookups = Vec::from_iter_in(lookups_in_cond.iter().map(|t| t.0), env.arena); + + let mut layouts = Vec::with_capacity_in(lookups_in_cond.len(), env.arena); + + for (_, var) in lookups_in_cond { + let res_layout = layout_cache.from_var(env.arena, var, env.subs); + let layout = return_on_layout_error!(env, res_layout, "Expect"); + layouts.push(layout); + } + + let mut stmt = Stmt::ExpectFx { + condition: cond_symbol, + region: loc_condition.region, + lookups: lookups.into_bump_slice(), + layouts: layouts.into_bump_slice(), + remainder: env.arena.alloc(rest), + }; + + stmt = with_hole( + env, + loc_condition.value, + variable, + procs, + layout_cache, + cond_symbol, + env.arena.alloc(stmt), + ); + + stmt + } + LetRec(defs, cont, _cycle_mark) => { // because Roc is strict, only functions can be recursive! for def in defs.into_iter() { @@ -6497,6 +6556,32 @@ fn substitute_in_stmt_help<'a>( Some(arena.alloc(expect)) } + ExpectFx { + condition, + region, + lookups, + layouts, + remainder, + } => { + let new_remainder = + substitute_in_stmt_help(arena, remainder, subs).unwrap_or(remainder); + + let new_lookups = Vec::from_iter_in( + lookups.iter().map(|s| substitute(subs, *s).unwrap_or(*s)), + arena, + ); + + let expect = ExpectFx { + condition: substitute(subs, *condition).unwrap_or(*condition), + region: *region, + lookups: new_lookups.into_bump_slice(), + layouts, + remainder: new_remainder, + }; + + Some(arena.alloc(expect)) + } + Jump(id, args) => { let mut did_change = false; let new_args = Vec::from_iter_in( diff --git a/crates/compiler/mono/src/reset_reuse.rs b/crates/compiler/mono/src/reset_reuse.rs index 44c77190f7..feec4fe8e4 100644 --- a/crates/compiler/mono/src/reset_reuse.rs +++ b/crates/compiler/mono/src/reset_reuse.rs @@ -216,6 +216,31 @@ fn function_s<'a, 'i>( } } + ExpectFx { + condition, + region, + lookups, + layouts, + remainder, + } => { + let continuation: &Stmt = *remainder; + let new_continuation = function_s(env, w, c, continuation); + + if std::ptr::eq(continuation, new_continuation) || continuation == new_continuation { + stmt + } else { + let new_refcounting = ExpectFx { + condition: *condition, + region: *region, + lookups, + layouts, + remainder: new_continuation, + }; + + arena.alloc(new_refcounting) + } + } + Ret(_) | Jump(_, _) | RuntimeError(_) => stmt, } } @@ -446,6 +471,39 @@ fn function_d_main<'a, 'i>( (arena.alloc(refcounting), found) } } + ExpectFx { + condition, + region, + lookups, + layouts, + remainder, + } => { + let (b, found) = function_d_main(env, x, c, remainder); + + if found || *condition != x { + let refcounting = ExpectFx { + condition: *condition, + region: *region, + lookups, + layouts, + remainder: b, + }; + + (arena.alloc(refcounting), found) + } else { + let b = try_function_s(env, x, c, b); + + let refcounting = ExpectFx { + condition: *condition, + region: *region, + lookups, + layouts, + remainder: b, + }; + + (arena.alloc(refcounting), found) + } + } Join { id, parameters, @@ -618,6 +676,26 @@ fn function_r<'a, 'i>(env: &mut Env<'a, 'i>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a> arena.alloc(expect) } + ExpectFx { + condition, + region, + lookups, + layouts, + remainder, + } => { + let b = function_r(env, remainder); + + let expect = ExpectFx { + condition: *condition, + region: *region, + lookups, + layouts, + remainder: b, + }; + + arena.alloc(expect) + } + Ret(_) | Jump(_, _) | RuntimeError(_) => { // terminals stmt @@ -653,6 +731,11 @@ fn has_live_var<'a>(jp_live_vars: &JPLiveVarMap, stmt: &'a Stmt<'a>, needle: Sym remainder, .. } => *condition == needle || has_live_var(jp_live_vars, remainder, needle), + ExpectFx { + condition, + remainder, + .. + } => *condition == needle || has_live_var(jp_live_vars, remainder, needle), Join { id, parameters, diff --git a/crates/compiler/mono/src/tail_recursion.rs b/crates/compiler/mono/src/tail_recursion.rs index 5d29f3f527..834c0dc6c8 100644 --- a/crates/compiler/mono/src/tail_recursion.rs +++ b/crates/compiler/mono/src/tail_recursion.rs @@ -273,6 +273,30 @@ fn insert_jumps<'a>( None => None, }, + ExpectFx { + condition, + region, + lookups, + layouts, + remainder, + } => match insert_jumps( + arena, + remainder, + goal_id, + needle, + needle_arguments, + needle_result, + ) { + Some(cont) => Some(arena.alloc(ExpectFx { + condition: *condition, + region: *region, + lookups, + layouts, + remainder: cont, + })), + None => None, + }, + Ret(_) => None, Jump(_, _) => None, RuntimeError(_) => None, diff --git a/crates/compiler/parse/src/expr.rs b/crates/compiler/parse/src/expr.rs index fceeda678a..832fe87251 100644 --- a/crates/compiler/parse/src/expr.rs +++ b/crates/compiler/parse/src/expr.rs @@ -636,7 +636,7 @@ fn parse_defs_end<'a>( let parse_expect_vanilla = crate::parser::keyword_e(crate::keyword::EXPECT, EExpect::Expect); let parse_expect_fx = crate::parser::keyword_e(crate::keyword::EXPECT_FX, EExpect::Expect); - let parse_expect = either!(parse_expect_vanilla, parse_expect_fx); + let parse_expect = either!(parse_expect_fx, parse_expect_vanilla); match space0_after_e( crate::pattern::loc_pattern_help(min_indent), @@ -682,11 +682,11 @@ fn parse_defs_end<'a>( let preceding_comment = Region::new(spaces_before_current_start, start); let value_def = match expect_flavor { - Either::First(_) => ValueDef::Expect { + Either::Second(_) => ValueDef::Expect { condition: arena.alloc(loc_def_expr), preceding_comment, }, - Either::Second(_) => ValueDef::ExpectFx { + Either::First(_) => ValueDef::ExpectFx { condition: arena.alloc(loc_def_expr), preceding_comment, }, diff --git a/crates/compiler/test_derive/src/pretty_print.rs b/crates/compiler/test_derive/src/pretty_print.rs index d263b3a15e..953ea05057 100644 --- a/crates/compiler/test_derive/src/pretty_print.rs +++ b/crates/compiler/test_derive/src/pretty_print.rs @@ -257,6 +257,7 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, ZeroArgumentTag { .. } => todo!(), OpaqueRef { .. } => todo!(), Expect { .. } => todo!(), + ExpectFx { .. } => todo!(), TypedHole(_) => todo!(), RuntimeError(_) => todo!(), } diff --git a/crates/repl_expect/Cargo.toml b/crates/repl_expect/Cargo.toml index 88c350a933..4b4451ba44 100644 --- a/crates/repl_expect/Cargo.toml +++ b/crates/repl_expect/Cargo.toml @@ -26,10 +26,11 @@ roc_build = { path = "../compiler/build" } libloading = "0.7.1" inkwell = { path = "../vendor/inkwell" } +signal-hook = "0.3.14" +libc = "0.2.106" [dev-dependencies] test_gen = { path = "../compiler/test_gen" } -libc = "0.2.106" roc_build = { path = "../compiler/build", features = ["target-aarch64", "target-x86_64"] } tempfile = "3.2.0" indoc = "1.0.7" diff --git a/crates/repl_expect/src/lib.rs b/crates/repl_expect/src/lib.rs index 5240bb8a1f..e52ca7d175 100644 --- a/crates/repl_expect/src/lib.rs +++ b/crates/repl_expect/src/lib.rs @@ -141,6 +141,7 @@ mod test { const BUFFER_SIZE: usize = 1024; let mut shared_buffer = [0u8; BUFFER_SIZE]; + let mut memory = crate::run::ExpectMemory::from_slice(&mut shared_buffer); // communicate the mmapped name to zig/roc let set_shared_buffer = run_roc_dylib!(lib, "set_shared_buffer", (*mut u8, usize), ()); @@ -148,15 +149,15 @@ mod test { unsafe { set_shared_buffer((shared_buffer.as_mut_ptr(), BUFFER_SIZE), &mut result) }; let mut writer = Vec::with_capacity(1024); - let (_failed, _passed) = crate::run::run_expects( + let (_failed, _passed) = crate::run::run_expects_with_memory( &mut writer, RenderTarget::ColorTerminal, arena, interns, &lib, &mut expectations, - shared_buffer.as_mut_ptr(), expects, + &mut memory, ) .unwrap(); diff --git a/crates/repl_expect/src/run.rs b/crates/repl_expect/src/run.rs index 86af7f5757..1d1cc232ce 100644 --- a/crates/repl_expect/src/run.rs +++ b/crates/repl_expect/src/run.rs @@ -1,9 +1,15 @@ +use std::os::unix::process::parent_id; + use bumpalo::collections::Vec as BumpVec; use bumpalo::Bump; use inkwell::context::Context; use roc_build::link::llvm_module_to_dylib; use roc_collections::{MutSet, VecMap}; -use roc_gen_llvm::llvm::{build::LlvmBackendMode, externs::add_default_roc_externs}; +use roc_gen_llvm::{ + llvm::{build::LlvmBackendMode, externs::add_default_roc_externs}, + run_roc::RocCallResult, + run_roc_dylib, +}; use roc_load::{EntryPoint, Expectations, MonomorphizedModule}; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_mono::ir::OptLevel; @@ -12,6 +18,67 @@ use roc_reporting::{error::expect::Renderer, report::RenderTarget}; use roc_target::TargetInfo; use target_lexicon::Triple; +pub(crate) struct ExpectMemory<'a> { + ptr: *mut u8, + length: usize, + shm_name: Option, + _marker: std::marker::PhantomData<&'a ()>, +} + +impl<'a> ExpectMemory<'a> { + const SHM_SIZE: usize = 1024; + + #[cfg(test)] + pub(crate) fn from_slice(slice: &mut [u8]) -> Self { + Self { + ptr: slice.as_mut_ptr(), + length: slice.len(), + shm_name: None, + _marker: std::marker::PhantomData, + } + } + + fn create_or_reuse_mmap(shm_name: &str) -> Self { + let cstring = std::ffi::CString::new(shm_name).unwrap(); + Self::mmap_help(cstring, libc::O_RDWR | libc::O_CREAT) + } + + fn reuse_mmap(&mut self) -> Option { + let shm_name = self.shm_name.as_ref()?.clone(); + Some(Self::mmap_help(shm_name, libc::O_RDWR)) + } + + fn mmap_help(cstring: std::ffi::CString, shm_flags: i32) -> Self { + let ptr = unsafe { + let shared_fd = libc::shm_open(cstring.as_ptr().cast(), shm_flags, 0o666); + + libc::ftruncate(shared_fd, Self::SHM_SIZE as _); + + libc::mmap( + std::ptr::null_mut(), + Self::SHM_SIZE, + libc::PROT_WRITE | libc::PROT_READ, + libc::MAP_SHARED, + shared_fd, + 0, + ) + }; + + Self { + ptr: ptr.cast(), + length: Self::SHM_SIZE, + shm_name: Some(cstring), + _marker: std::marker::PhantomData, + } + } + + fn set_shared_buffer(&mut self, lib: &libloading::Library) { + let set_shared_buffer = run_roc_dylib!(lib, "set_shared_buffer", (*mut u8, usize), ()); + let mut result = RocCallResult::default(); + unsafe { set_shared_buffer((self.ptr, self.length), &mut result) }; + } +} + #[allow(clippy::too_many_arguments)] pub fn run_expects( writer: &mut W, @@ -20,21 +87,66 @@ pub fn run_expects( interns: &Interns, lib: &libloading::Library, expectations: &mut VecMap, - shared_ptr: *mut u8, - expects: bumpalo::collections::Vec<'_, ToplevelExpect<'_>>, + expects: ExpectFunctions<'_>, +) -> std::io::Result<(usize, usize)> { + let shm_name = format!("/roc_expect_buffer_{}", std::process::id()); + let mut memory = ExpectMemory::create_or_reuse_mmap(&shm_name); + + run_expects_with_memory( + writer, + render_target, + arena, + interns, + lib, + expectations, + expects, + &mut memory, + ) +} + +#[allow(clippy::too_many_arguments)] +pub(crate) fn run_expects_with_memory( + writer: &mut W, + render_target: RenderTarget, + arena: &Bump, + interns: &Interns, + lib: &libloading::Library, + expectations: &mut VecMap, + expects: ExpectFunctions<'_>, + memory: &mut ExpectMemory, ) -> std::io::Result<(usize, usize)> { let mut failed = 0; let mut passed = 0; - for expect in expects { - let result = run_expect( + for expect in expects.fx { + let result = run_expect_fx( writer, render_target, arena, interns, lib, expectations, - shared_ptr, + memory, + expect, + )?; + + match result { + true => passed += 1, + false => failed += 1, + } + } + + memory.set_shared_buffer(lib); + + for expect in expects.pure { + let result = run_expect_pure( + writer, + render_target, + arena, + interns, + lib, + expectations, + memory, expect, )?; @@ -48,23 +160,23 @@ pub fn run_expects( } #[allow(clippy::too_many_arguments)] -fn run_expect( +fn run_expect_pure( writer: &mut W, render_target: RenderTarget, arena: &Bump, interns: &Interns, lib: &libloading::Library, expectations: &mut VecMap, - shared_ptr: *mut u8, + shared_memory: &mut ExpectMemory, expect: ToplevelExpect<'_>, ) -> std::io::Result { use roc_gen_llvm::try_run_jit_function; - let sequence = ExpectSequence::new(shared_ptr.cast()); + let sequence = ExpectSequence::new(shared_memory.ptr.cast()); let result: Result<(), String> = try_run_jit_function!(lib, expect.name, (), |v: ()| v); - let shared_memory_ptr: *const u8 = shared_ptr.cast(); + let shared_memory_ptr: *const u8 = shared_memory.ptr.cast(); if result.is_err() || sequence.count_failures() > 0 { let module_id = expect.symbol.module_id(); @@ -103,6 +215,104 @@ fn run_expect( } } +#[allow(clippy::too_many_arguments)] +fn run_expect_fx( + writer: &mut W, + render_target: RenderTarget, + arena: &Bump, + interns: &Interns, + lib: &libloading::Library, + expectations: &mut VecMap, + parent_memory: &mut ExpectMemory, + expect: ToplevelExpect<'_>, +) -> std::io::Result { + use signal_hook::{consts::signal::SIGCHLD, consts::signal::SIGUSR1, iterator::Signals}; + + let mut signals = Signals::new(&[SIGCHLD, SIGUSR1]).unwrap(); + + match unsafe { libc::fork() } { + 0 => unsafe { + // we are the child + + use roc_gen_llvm::try_run_jit_function; + + let mut child_memory = parent_memory.reuse_mmap().unwrap(); + + let sequence = ExpectSequence::new(child_memory.ptr); + + child_memory.set_shared_buffer(lib); + + let result: Result<(), String> = try_run_jit_function!(lib, expect.name, (), |v: ()| v); + + if let Err(msg) = result { + panic!("roc panic {}", msg); + } + + if sequence.count_failures() > 0 { + libc::kill(parent_id() as _, SIGUSR1); + } + + std::process::exit(0) + }, + -1 => { + // something failed + + // Display a human-friendly error message + println!("Error {:?}", std::io::Error::last_os_error()); + + std::process::exit(1) + } + 1.. => { + let mut has_succeeded = true; + + for sig in &mut signals { + match sig { + SIGCHLD => { + // done! + return Ok(has_succeeded); + } + SIGUSR1 => { + // this is the signal we use for an expect failure. Let's see what the child told us + has_succeeded = false; + + let frame = + ExpectFrame::at_offset(parent_memory.ptr, ExpectSequence::START_OFFSET); + let module_id = frame.module_id; + + let data = expectations.get_mut(&module_id).unwrap(); + let filename = data.path.to_owned(); + let source = std::fs::read_to_string(&data.path).unwrap(); + + let renderer = Renderer::new( + arena, + interns, + render_target, + module_id, + filename, + &source, + ); + + render_expect_failure( + writer, + &renderer, + arena, + None, + expectations, + interns, + parent_memory.ptr, + ExpectSequence::START_OFFSET, + )?; + } + _ => println!("received signal {}", sig), + } + } + + Ok(true) + } + _ => unreachable!(), + } +} + pub fn roc_dev_expect( writer: &mut impl std::io::Write, arena: &Bump, @@ -205,8 +415,8 @@ impl ExpectSequence { fn new(ptr: *mut u8) -> Self { unsafe { let ptr = ptr as *mut usize; - *ptr.add(Self::COUNT_INDEX) = 0; - *ptr.add(Self::OFFSET_INDEX) = Self::START_OFFSET; + std::ptr::write_unaligned(ptr.add(Self::COUNT_INDEX), 0); + std::ptr::write_unaligned(ptr.add(Self::OFFSET_INDEX), Self::START_OFFSET); } Self { @@ -251,13 +461,19 @@ pub struct ToplevelExpect<'a> { pub region: Region, } +#[derive(Debug)] +pub struct ExpectFunctions<'a> { + pub pure: BumpVec<'a, ToplevelExpect<'a>>, + pub fx: BumpVec<'a, ToplevelExpect<'a>>, +} + pub fn expect_mono_module_to_dylib<'a>( arena: &'a Bump, target: Triple, loaded: MonomorphizedModule<'a>, opt_level: OptLevel, mode: LlvmBackendMode, -) -> Result<(libloading::Library, BumpVec<'a, ToplevelExpect<'a>>), libloading::Error> { +) -> Result<(libloading::Library, ExpectFunctions<'a>), libloading::Error> { let target_info = TargetInfo::from(&target); let MonomorphizedModule { @@ -306,18 +522,25 @@ pub fn expect_mono_module_to_dylib<'a>( EntryPoint::Test => None, }; + let capacity = toplevel_expects.pure.len() + toplevel_expects.fx.len(); + let mut expect_symbols = BumpVec::with_capacity_in(capacity, env.arena); + + expect_symbols.extend(toplevel_expects.pure.keys().copied()); + expect_symbols.extend(toplevel_expects.fx.keys().copied()); + let expect_names = roc_gen_llvm::llvm::build::build_procedures_expose_expects( &env, opt_level, - toplevel_expects.unzip_slices().0, + &expect_symbols, procedures, opt_entry_point, ); - let expects = bumpalo::collections::Vec::from_iter_in( + let expects_fx = bumpalo::collections::Vec::from_iter_in( toplevel_expects + .fx .into_iter() - .zip(expect_names.into_iter()) + .zip(expect_names.iter().skip(toplevel_expects.pure.len())) .map(|((symbol, region), name)| ToplevelExpect { symbol, region, @@ -326,6 +549,24 @@ pub fn expect_mono_module_to_dylib<'a>( env.arena, ); + let expects_pure = bumpalo::collections::Vec::from_iter_in( + toplevel_expects + .pure + .into_iter() + .zip(expect_names.iter()) + .map(|((symbol, region), name)| ToplevelExpect { + symbol, + region, + name, + }), + env.arena, + ); + + let expects = ExpectFunctions { + pure: expects_pure, + fx: expects_fx, + }; + env.dibuilder.finalize(); // we don't use the debug info, and it causes weird errors.