diff --git a/Cargo.lock b/Cargo.lock index 256cbe02628..904d6d5066b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3669,6 +3669,7 @@ dependencies = [ name = "roc_mono" version = "0.0.1" dependencies = [ + "arrayvec 0.7.2", "bitvec", "bumpalo", "hashbrown 0.13.2", diff --git a/crates/compiler/can/src/builtins.rs b/crates/compiler/can/src/builtins.rs index cf8ca72ee94..656f9cd1b42 100644 --- a/crates/compiler/can/src/builtins.rs +++ b/crates/compiler/can/src/builtins.rs @@ -87,7 +87,7 @@ macro_rules! map_symbol_to_lowlevel_and_arity { LowLevel::PtrCast => unimplemented!(), LowLevel::PtrStore => unimplemented!(), LowLevel::PtrLoad => unimplemented!(), - LowLevel::PtrToStackValue => unimplemented!(), + LowLevel::Alloca => unimplemented!(), LowLevel::RefCountIncRcPtr => unimplemented!(), LowLevel::RefCountDecRcPtr=> unimplemented!(), LowLevel::RefCountIncDataPtr => unimplemented!(), diff --git a/crates/compiler/collections/src/vec_set.rs b/crates/compiler/collections/src/vec_set.rs index c45a8169248..2a19bf2b9c7 100644 --- a/crates/compiler/collections/src/vec_set.rs +++ b/crates/compiler/collections/src/vec_set.rs @@ -102,16 +102,6 @@ impl VecSet { { self.elements.retain(f) } - - pub fn keep_if_in_both(&mut self, other: &Self) { - self.elements.retain(|e| other.contains(e)); - } - - pub fn keep_if_in_either(&mut self, other: Self) { - for e in other.elements { - self.insert(e); - } - } } impl Extend for VecSet { diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 76aa3e3f9bb..90804dbc0f8 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -2791,14 +2791,7 @@ impl< .storage_manager .load_to_general_reg(&mut self.buf, structure); - let mask_symbol = self.debug_symbol("tag_id_mask"); - let mask_reg = self - .storage_manager - .claim_general_reg(&mut self.buf, &mask_symbol); - ASM::mov_reg64_imm64(&mut self.buf, mask_reg, (!0b111) as _); - - // mask out the tag id bits - ASM::and_reg64_reg64_reg64(&mut self.buf, mask_reg, ptr_reg, mask_reg); + let (mask_symbol, mask_reg) = self.clear_tag_id(ptr_reg); let mut offset = 0; for field in &other_fields[..index as usize] { @@ -2814,6 +2807,8 @@ impl< element_layout, *sym, ); + + self.free_symbol(&mask_symbol) } UnionLayout::Recursive(tag_layouts) => { let other_fields = tag_layouts[tag_id as usize]; @@ -2824,22 +2819,13 @@ impl< .load_to_general_reg(&mut self.buf, structure); // mask out the tag id bits - let unmasked_reg = if union_layout - .stores_tag_id_as_data(self.storage_manager.target_info) - { - ptr_reg - } else { - let umasked_symbol = self.debug_symbol("unmasked"); - let unmasked_reg = self - .storage_manager - .claim_general_reg(&mut self.buf, &umasked_symbol); - - ASM::mov_reg64_imm64(&mut self.buf, unmasked_reg, (!0b111) as _); - - ASM::and_reg64_reg64_reg64(&mut self.buf, unmasked_reg, ptr_reg, unmasked_reg); - - unmasked_reg - }; + let (unmasked_symbol, unmasked_reg) = + if union_layout.stores_tag_id_as_data(self.storage_manager.target_info) { + (None, ptr_reg) + } else { + let (mask_symbol, mask_reg) = self.clear_tag_id(ptr_reg); + (Some(mask_symbol), mask_reg) + }; let mut offset = 0; for field in &other_fields[..index as usize] { @@ -2855,6 +2841,10 @@ impl< element_layout, *sym, ); + + if let Some(unmasked_symbol) = unmasked_symbol { + self.free_symbol(&unmasked_symbol); + } } } } @@ -2911,14 +2901,7 @@ impl< other_tags[tag_id as usize - 1] }; - let mask_symbol = self.debug_symbol("tag_id_mask"); - let mask_reg = self - .storage_manager - .claim_general_reg(&mut self.buf, &mask_symbol); - ASM::mov_reg64_imm64(&mut self.buf, mask_reg, (!0b111) as _); - - // mask out the tag id bits - ASM::and_reg64_reg64_reg64(&mut self.buf, mask_reg, ptr_reg, mask_reg); + let (mask_symbol, mask_reg) = self.clear_tag_id(ptr_reg); let mut offset = 0; for field in &other_fields[..index as usize] { @@ -2926,6 +2909,8 @@ impl< } ASM::add_reg64_reg64_imm32(&mut self.buf, sym_reg, mask_reg, offset as i32); + + self.free_symbol(&mask_symbol); } UnionLayout::Recursive(tag_layouts) => { let other_fields = tag_layouts[tag_id as usize]; @@ -2935,22 +2920,13 @@ impl< .load_to_general_reg(&mut self.buf, structure); // mask out the tag id bits - let unmasked_reg = if union_layout - .stores_tag_id_as_data(self.storage_manager.target_info) - { - ptr_reg - } else { - let umasked_symbol = self.debug_symbol("unmasked"); - let unmasked_reg = self - .storage_manager - .claim_general_reg(&mut self.buf, &umasked_symbol); - - ASM::mov_reg64_imm64(&mut self.buf, unmasked_reg, (!0b111) as _); - - ASM::and_reg64_reg64_reg64(&mut self.buf, unmasked_reg, ptr_reg, unmasked_reg); - - unmasked_reg - }; + let (unmasked_symbol, unmasked_reg) = + if union_layout.stores_tag_id_as_data(self.storage_manager.target_info) { + (None, ptr_reg) + } else { + let (mask_symbol, mask_reg) = self.clear_tag_id(ptr_reg); + (Some(mask_symbol), mask_reg) + }; let mut offset = 0; for field in &other_fields[..index as usize] { @@ -2958,6 +2934,10 @@ impl< } ASM::add_reg64_reg64_imm32(&mut self.buf, sym_reg, unmasked_reg, offset as i32); + + if let Some(unmasked_symbol) = unmasked_symbol { + self.free_symbol(&unmasked_symbol); + } } } } @@ -3986,6 +3966,19 @@ impl< CC: CallConv, > Backend64Bit<'a, 'r, GeneralReg, FloatReg, ASM, CC> { + fn clear_tag_id(&mut self, ptr_reg: GeneralReg) -> (Symbol, GeneralReg) { + let unmasked_symbol = self.debug_symbol("unmasked"); + let unmasked_reg = self + .storage_manager + .claim_general_reg(&mut self.buf, &unmasked_symbol); + + ASM::mov_reg64_imm64(&mut self.buf, unmasked_reg, (!0b111) as _); + + ASM::and_reg64_reg64_reg64(&mut self.buf, unmasked_reg, ptr_reg, unmasked_reg); + + (unmasked_symbol, unmasked_reg) + } + fn compare( &mut self, op: CompareOperation, diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index dab8eb6b93a..25bd620e95e 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -1605,7 +1605,7 @@ trait Backend<'a> { self.build_ptr_load(*sym, args[0], *ret_layout); } - LowLevel::PtrToStackValue => { + LowLevel::Alloca => { self.build_ptr_to_stack_value(*sym, args[0], arg_layouts[0]); } diff --git a/crates/compiler/gen_llvm/src/llvm/build.rs b/crates/compiler/gen_llvm/src/llvm/build.rs index 9718c8863ce..38a06b4dac7 100644 --- a/crates/compiler/gen_llvm/src/llvm/build.rs +++ b/crates/compiler/gen_llvm/src/llvm/build.rs @@ -1502,7 +1502,7 @@ pub(crate) fn build_exp_expr<'a, 'ctx>( let ptr = tag_pointer_clear_tag_id(env, argument.into_pointer_value()); let target_loaded_type = basic_type_from_layout(env, layout_interner, ret_repr); - union_field_at_index( + union_field_ptr_at_index( env, layout_interner, field_layouts, @@ -1518,7 +1518,7 @@ pub(crate) fn build_exp_expr<'a, 'ctx>( let struct_type = basic_type_from_layout(env, layout_interner, struct_layout); let target_loaded_type = basic_type_from_layout(env, layout_interner, ret_repr); - union_field_at_index( + union_field_ptr_at_index( env, layout_interner, field_layouts, @@ -1546,7 +1546,7 @@ pub(crate) fn build_exp_expr<'a, 'ctx>( let ptr = tag_pointer_clear_tag_id(env, argument.into_pointer_value()); let target_loaded_type = basic_type_from_layout(env, layout_interner, ret_repr); - union_field_at_index( + union_field_ptr_at_index( env, layout_interner, field_layouts, @@ -1569,7 +1569,7 @@ pub(crate) fn build_exp_expr<'a, 'ctx>( let struct_type = basic_type_from_layout(env, layout_interner, struct_layout); let target_loaded_type = basic_type_from_layout(env, layout_interner, ret_repr); - union_field_at_index( + union_field_ptr_at_index( env, layout_interner, field_layouts, @@ -2156,7 +2156,7 @@ fn lookup_at_index_ptr<'a, 'ctx>( struct_type: Option>, target_loaded_type: BasicTypeEnum<'ctx>, ) -> BasicValueEnum<'ctx> { - let elem_ptr = union_field_at_index_help( + let elem_ptr = union_field_ptr_at_index_help( env, layout_interner, field_layouts, @@ -2179,7 +2179,7 @@ fn lookup_at_index_ptr<'a, 'ctx>( cast_if_necessary_for_opaque_recursive_pointers(env.builder, result, target_loaded_type) } -fn union_field_at_index_help<'a, 'ctx>( +fn union_field_ptr_at_index_help<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, field_layouts: &'a [InLayout<'a>], @@ -2213,7 +2213,7 @@ fn union_field_at_index_help<'a, 'ctx>( .unwrap() } -fn union_field_at_index<'a, 'ctx>( +fn union_field_ptr_at_index<'a, 'ctx>( env: &Env<'a, 'ctx, '_>, layout_interner: &STLayoutInterner<'a>, field_layouts: &'a [InLayout<'a>], @@ -2222,7 +2222,7 @@ fn union_field_at_index<'a, 'ctx>( value: PointerValue<'ctx>, target_loaded_type: BasicTypeEnum<'ctx>, ) -> PointerValue<'ctx> { - let result = union_field_at_index_help( + let result = union_field_ptr_at_index_help( env, layout_interner, field_layouts, diff --git a/crates/compiler/gen_llvm/src/llvm/lowlevel.rs b/crates/compiler/gen_llvm/src/llvm/lowlevel.rs index 241d0e7a537..63a9a15e740 100644 --- a/crates/compiler/gen_llvm/src/llvm/lowlevel.rs +++ b/crates/compiler/gen_llvm/src/llvm/lowlevel.rs @@ -1325,7 +1325,7 @@ pub(crate) fn run_low_level<'a, 'ctx>( .new_build_load(element_type, ptr.into_pointer_value(), "ptr_load") } - PtrToStackValue => { + Alloca => { arguments!(initial_value); let ptr = entry_block_alloca_zerofill(env, initial_value.get_type(), "stack_value"); diff --git a/crates/compiler/gen_wasm/src/low_level.rs b/crates/compiler/gen_wasm/src/low_level.rs index 8e76474d6dd..f87807a6da6 100644 --- a/crates/compiler/gen_wasm/src/low_level.rs +++ b/crates/compiler/gen_wasm/src/low_level.rs @@ -1979,8 +1979,8 @@ impl<'a> LowLevelCall<'a> { ); } PtrLoad => backend.expr_unbox(self.ret_symbol, self.arguments[0]), - PtrToStackValue => { - // PtrToStackValue : a -> Ptr a + Alloca => { + // Alloca : a -> Ptr a let arg = self.arguments[0]; let arg_layout = backend.storage.symbol_layouts.get(&arg).unwrap(); diff --git a/crates/compiler/module/src/low_level.rs b/crates/compiler/module/src/low_level.rs index a8b68cd4f34..bbb30d075cd 100644 --- a/crates/compiler/module/src/low_level.rs +++ b/crates/compiler/module/src/low_level.rs @@ -120,7 +120,7 @@ pub enum LowLevel { PtrCast, PtrStore, PtrLoad, - PtrToStackValue, + Alloca, RefCountIncRcPtr, RefCountDecRcPtr, RefCountIncDataPtr, @@ -232,7 +232,7 @@ macro_rules! map_symbol_to_lowlevel { LowLevel::PtrCast => unimplemented!(), LowLevel::PtrStore => unimplemented!(), LowLevel::PtrLoad => unimplemented!(), - LowLevel::PtrToStackValue => unimplemented!(), + LowLevel::Alloca => unimplemented!(), LowLevel::RefCountIncRcPtr => unimplemented!(), LowLevel::RefCountDecRcPtr=> unimplemented!(), LowLevel::RefCountIncDataPtr => unimplemented!(), diff --git a/crates/compiler/mono/Cargo.toml b/crates/compiler/mono/Cargo.toml index 44887a5b8f6..2fd6db34d93 100644 --- a/crates/compiler/mono/Cargo.toml +++ b/crates/compiler/mono/Cargo.toml @@ -27,6 +27,7 @@ roc_types = { path = "../types" } ven_pretty = { path = "../../vendor/pretty" } bitvec.workspace = true +arrayvec.workspace = true bumpalo.workspace = true hashbrown.workspace = true parking_lot.workspace = true diff --git a/crates/compiler/mono/src/borrow.rs b/crates/compiler/mono/src/borrow.rs index cb2b9aeb5bf..bd03651ecc2 100644 --- a/crates/compiler/mono/src/borrow.rs +++ b/crates/compiler/mono/src/borrow.rs @@ -1045,7 +1045,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[Ownership] { PtrStore => arena.alloc_slice_copy(&[owned, owned]), PtrLoad => arena.alloc_slice_copy(&[owned]), - PtrToStackValue => arena.alloc_slice_copy(&[owned]), + Alloca => arena.alloc_slice_copy(&[owned]), PtrCast | RefCountIncRcPtr | RefCountDecRcPtr | RefCountIncDataPtr | RefCountDecDataPtr | RefCountIsUnique => { diff --git a/crates/compiler/mono/src/drop_specialization.rs b/crates/compiler/mono/src/drop_specialization.rs index 2be4cfb76e6..6639440583c 100644 --- a/crates/compiler/mono/src/drop_specialization.rs +++ b/crates/compiler/mono/src/drop_specialization.rs @@ -1680,7 +1680,7 @@ fn low_level_no_rc(lowlevel: &LowLevel) -> RC { // only inserted for internal purposes. RC should not touch it PtrStore => RC::NoRc, PtrLoad => RC::NoRc, - PtrToStackValue => RC::NoRc, + Alloca => RC::NoRc, PtrCast | RefCountIncRcPtr | RefCountDecRcPtr | RefCountIncDataPtr | RefCountDecDataPtr | RefCountIsUnique => { diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index 5c6ba99250c..4e417b3cdaf 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -7659,6 +7659,7 @@ fn substitute_in_expr<'a>( None => None, }, + // currently only used for tail recursion modulo cons (TRMC) UnionFieldPtrAtIndex { structure, tag_id, diff --git a/crates/compiler/mono/src/layout.rs b/crates/compiler/mono/src/layout.rs index 905b1cbb280..afa9abaa646 100644 --- a/crates/compiler/mono/src/layout.rs +++ b/crates/compiler/mono/src/layout.rs @@ -674,7 +674,10 @@ pub(crate) enum LayoutWrapper<'a> { pub enum LayoutRepr<'a> { Builtin(Builtin<'a>), Struct(&'a [InLayout<'a>]), + // A (heap allocated) reference-counted value Boxed(InLayout<'a>), + // A pointer (heap or stack) without any reference counting + // Ptr is not user-facing. The compiler author must make sure that invariants are upheld Ptr(InLayout<'a>), Union(UnionLayout<'a>), LambdaSet(LambdaSet<'a>), diff --git a/crates/compiler/mono/src/tail_recursion.rs b/crates/compiler/mono/src/tail_recursion.rs index 0ae595579be..829b4d157f6 100644 --- a/crates/compiler/mono/src/tail_recursion.rs +++ b/crates/compiler/mono/src/tail_recursion.rs @@ -10,7 +10,7 @@ use crate::layout::{ }; use bumpalo::collections::Vec; use bumpalo::Bump; -use roc_collections::{MutMap, VecSet}; +use roc_collections::{MutMap, VecMap, VecSet}; use roc_module::low_level::LowLevel; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; @@ -423,15 +423,6 @@ impl TrmcCandidateSet { self.active.insert(call); } - fn extend(&mut self, other: Self) { - self.confirmed.keep_if_in_either(other.confirmed); - self.invalid.keep_if_in_either(other.invalid); - self.active.keep_if_in_either(other.active); - - self.active.retain(|k| !self.invalid.contains(k)); - self.confirmed.retain(|k| !self.invalid.contains(k)); - } - fn retain(&mut self, keep: F) where F: Fn(&Symbol) -> bool, @@ -443,7 +434,7 @@ impl TrmcCandidateSet { } self.active.retain(|k| !self.invalid.contains(k)); - self.confirmed.retain(|k| !self.invalid.contains(k)); + debug_assert!(!self.confirmed.iter().any(|x| self.invalid.contains(x))); } } @@ -465,26 +456,31 @@ where return VecSet::default(); } - trmc_candidates_help(proc.name, &proc.body, TrmcCandidateSet::default()).confirmed + let mut candidate_set = TrmcCandidateSet::default(); + trmc_candidates_help(proc.name, &proc.body, &mut candidate_set); + candidate_set.confirmed } fn trmc_candidates_help<'a>( function_name: LambdaName, stmt: &'_ Stmt<'a>, - mut candidates: TrmcCandidateSet, -) -> TrmcCandidateSet { + candidates: &mut TrmcCandidateSet, +) { // if this stmt is the literal tail tag application and return, then this is a TRMC opportunity if let Some(cons_info) = TrmcEnv::is_terminal_constructor(stmt) { - // must use the result of a recursive call directly as an argument - // we pick the (syntactically) first one - for recursive_call in candidates.active.iter() { - if cons_info.arguments.contains(recursive_call) { - return TrmcCandidateSet { - confirmed: VecSet::singleton(*recursive_call), - active: VecSet::default(), - invalid: candidates.invalid, - }; - } + // the tag application must directly use the result of the recursive call + let recursive_call = candidates + .active + .iter() + .copied() + .find(|call| cons_info.arguments.contains(call)); + + // if we find a usage, this is a confirmed TRMC call + if let Some(recursive_call) = recursive_call { + candidates.active.remove(&recursive_call); + candidates.confirmed.insert(recursive_call); + + return; } } @@ -511,15 +507,9 @@ fn trmc_candidates_help<'a>( .map(|(_, _, stmt)| stmt) .chain([default_branch.1]); - let mut accum = candidates.clone(); - for next in it { - let x = trmc_candidates_help(function_name, next, candidates.clone()); - - accum.extend(x); + trmc_candidates_help(function_name, next, candidates); } - - accum } Stmt::Refcounting(_, next) => trmc_candidates_help(function_name, next, candidates), Stmt::Expect { remainder, .. } @@ -528,26 +518,63 @@ fn trmc_candidates_help<'a>( Stmt::Join { body, remainder, .. } => { - let mut x = trmc_candidates_help(function_name, body, candidates.clone()); - let y = trmc_candidates_help(function_name, remainder, candidates.clone()); - - x.extend(y); - - x + trmc_candidates_help(function_name, body, candidates); + trmc_candidates_help(function_name, remainder, candidates); } - Stmt::Ret(_) | Stmt::Jump(_, _) | Stmt::Crash(_, _) => candidates, + Stmt::Ret(_) | Stmt::Jump(_, _) | Stmt::Crash(_, _) => { /* terminal */ } } } +// TRMC (tail recursion modulo constructor) is an optimization for some recursive functions that return a recursive data type. The most basic example is a repeat function on linked lists: +// +// ```roc +// LinkedList a : [ Nil, Cons a (LinkedList a) ] +// +// repeat : a, Nat -> LinkedList a +// repeat = \element, n -> +// when n is +// 0 -> Nil +// _ -> Cons element (repeat element (n - 1)) +// ``` +// +// This function is recursive, but cannot use standard tail-call elimintation, because the recursive call is not in tail position (i.e. the last thing happening before a return). Rather the recursive call is an argument to a constructor of the recursive output type. This means that `repeat n` will creat `n` stack frames. For big inputs, a stack overflow is inevitable. +// +// But there is a trick: TRMC. Using TRMC and join points, we are able to convert this function into a loop, which uses only one stack frame for the whole process. +// +// ```pseudo-roc +// repeat : a, Nat -> LinkedList a +// repeat = \initialElement, initialN -> +// joinpoint trmc = \element, n, hole, head -> +// when n is +// 0 -> +// # write the value `Nil` into the hole +// *hole = Nil +// # dereference (load from) the pointer to the first element +// *head +// +// _ -> +// *hole = Cons element NULL +// newHole = &hole.Cons.1 +// jump trmc element (n - 1) newHole head +// in +// # creates a stack allocation, gives a pointer to that stack allocation +// initial : Ptr (LinkedList a) = #alloca NULL +// jump trmc initialElement initialN initial initial +// ``` +// +// The functionality here figures out whether this transformation can be applied in valid way, and then performs the transformation. + #[derive(Clone)] pub(crate) struct TrmcEnv<'a> { + /// Current hole to fill hole_symbol: Symbol, + /// Pointer to the first constructor ("the head of the list") head_symbol: Symbol, joinpoint_id: JoinPointId, return_layout: InLayout<'a>, ptr_return_layout: InLayout<'a>, - trmc_calls: MutMap>>, + trmc_calls: VecMap>>, } #[derive(Debug)] @@ -599,7 +626,7 @@ impl<'a> TrmcEnv<'a> { fn is_recursive_call(call: &Call<'a>, lambda_name: LambdaName<'_>) -> bool { match call.call_type { CallType::ByName { name, .. } => { - // TODO are there other restrictions? + // because we do not allow polymorphic recursion, this is the only constraint name == lambda_name } CallType::Foreign { .. } | CallType::LowLevel { .. } | CallType::HigherOrder(_) => { @@ -617,7 +644,6 @@ impl<'a> TrmcEnv<'a> { let ptr_write = Call { call_type: crate::ir::CallType::LowLevel { op: LowLevel::PtrStore, - // update_mode: env.next_update_mode_id(), update_mode: UpdateModeId::BACKEND_DUMMY, }, arguments: env.arena.alloc([ptr, value]), @@ -639,9 +665,9 @@ impl<'a> TrmcEnv<'a> { let arena = env.arena; let return_layout = proc.ret_layout; - let mut joinpoint_parameters = Vec::with_capacity_in(proc.args.len() + 1, env.arena); + let mut joinpoint_parameters = Vec::with_capacity_in(proc.args.len() + 2, env.arena); let mut new_proc_arguments = Vec::with_capacity_in(proc.args.len(), env.arena); - let mut jump_arguments = Vec::with_capacity_in(proc.args.len() + 1, env.arena); + let mut jump_arguments = Vec::with_capacity_in(proc.args.len() + 2, env.arena); for (i, (layout, old_symbol)) in proc.args.iter().enumerate() { let symbol = env.named_unique_symbol(&format!("arg_{i}")); @@ -670,7 +696,7 @@ impl<'a> TrmcEnv<'a> { let call = Call { call_type: CallType::LowLevel { - op: LowLevel::PtrToStackValue, + op: LowLevel::Alloca, update_mode: UpdateModeId::BACKEND_DUMMY, }, arguments: arena.alloc([null_symbol]), @@ -744,9 +770,13 @@ impl<'a> TrmcEnv<'a> { match stmt { Stmt::Let(symbol, expr, layout, next) => { - // if this is a TRMC call, + // if this is a TRMC call, remember what the call looks like, so we can turn it + // into a jump later. The call is then removed from the Stmt if let Some(opt_call) = self.trmc_calls.get_mut(symbol) { - debug_assert!(opt_call.is_none()); + debug_assert!( + opt_call.is_none(), + "didn't expect to visit call again since symbols are unique" + ); let call = match expr { Expr::Call(call) => call, diff --git a/crates/compiler/test_mono/generated/linked_list_map.txt b/crates/compiler/test_mono/generated/linked_list_map.txt index 8519c182987..9626e531966 100644 --- a/crates/compiler/test_mono/generated/linked_list_map.txt +++ b/crates/compiler/test_mono/generated/linked_list_map.txt @@ -9,7 +9,7 @@ procedure Test.10 (Test.11): procedure Test.2 (#Derived_gen.0, #Derived_gen.1): let #Derived_gen.3 : [, C I64 *self] = NullPointer; - let #Derived_gen.2 : Ptr([, C I64 *self]) = lowlevel PtrToStackValue #Derived_gen.3; + let #Derived_gen.2 : Ptr([, C I64 *self]) = lowlevel Alloca #Derived_gen.3; joinpoint #Derived_gen.4 Test.4 Test.5 #Derived_gen.5 #Derived_gen.6: let Test.22 : U8 = 1i64; let Test.23 : U8 = GetTagId Test.5; diff --git a/crates/compiler/test_mono/generated/rb_tree_fbip.txt b/crates/compiler/test_mono/generated/rb_tree_fbip.txt index e6cb057d392..e2db544d2d9 100644 --- a/crates/compiler/test_mono/generated/rb_tree_fbip.txt +++ b/crates/compiler/test_mono/generated/rb_tree_fbip.txt @@ -8,7 +8,7 @@ procedure Num.24 (#Attr.2, #Attr.3): procedure Test.3 (#Derived_gen.0, #Derived_gen.1, #Derived_gen.2): let #Derived_gen.4 : [C *self I64 *self I32 Int1, ] = NullPointer; - let #Derived_gen.3 : Ptr([C *self I64 *self I32 Int1, ]) = lowlevel PtrToStackValue #Derived_gen.4; + let #Derived_gen.3 : Ptr([C *self I64 *self I32 Int1, ]) = lowlevel Alloca #Derived_gen.4; joinpoint #Derived_gen.5 Test.9 Test.10 Test.11 #Derived_gen.6 #Derived_gen.7: let Test.254 : U8 = 0i64; let Test.255 : U8 = GetTagId Test.9;