use crate::borrow::{ParamMap, BORROWED, OWNED}; use crate::ir::{ CallType, Expr, HigherOrderLowLevel, JoinPointId, ModifyRc, Param, Proc, ProcLayout, Stmt, UpdateModeIds, }; use crate::layout::Layout; use bumpalo::collections::Vec; use bumpalo::Bump; use roc_collections::all::{MutMap, MutSet}; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; /// Data and Function ownership relation for higher-order lowlevels /// /// Normally, the borrowing algorithm figures out how to own/borrow data so that /// the borrows match up. But that fails for our higher-order lowlevels, because /// they are rigid (cannot add extra inc/dec in there dynamically) and opaque to /// the borrow inference. /// /// So, we must fix this up ourselves. This code is deliberately verbose to make /// it easier to understand without full context. /// /// If we take `List.map list f` as an example, then there are two orders of freedom: /// /// - `list` can be either owned or borrowed by `List.map` /// - `f` can require either owned or borrowed elements from `list` /// /// Hence we get the four options below: the data (`list` in the example) is owned or borrowed by /// the higher-order function, and the function argument (`f`) either owns or borrows the elements. #[allow(clippy::enum_variant_names)] #[derive(Debug, Clone, Copy)] #[repr(u8)] enum DataFunction { /// `list` is owned, and `f` expects owned values. That means that when we run the map, all /// list elements will be consumed (because they are passed to `f`, which takes owned values) /// Because we own the whole list, and must consume it, we need to `decref` the list. /// `decref` just decrements the container, and will never recursively decrement elements DataOwnedFunctionOwns, /// `list` is owned, and `f` expects borrowed values. After running `f` for each element, the /// elements are not consumed, and neither is the list. We must consume it though, because we /// own the `list`. Therefore we need to perform a `dec` DataOwnedFunctionBorrows, /// `list` is borrowed, `f` borrows, so the trivial implementation is correct: just map `f` /// over elements of `list`, and don't do anything else. DataBorrowedFunctionBorrows, /// The trickiest case: we only borrow the `list`, but the mapped function `f` needs owned /// values. There are two options /// /// - define some `fBorrow` that takes a borrowed value, `inc`'s it (similar to `.clone()` on /// an `Rc` in rust) and then passes the (now owned) value to `f`, then rewrite the call /// to `List.map list fBorrow` /// - `inc` the list (which recursively increments the elements), map `f` over the list, /// consuming one RC token on the elements, finally `decref` the list. /// /// For simplicity, we use the second option right now, but the first option is probably /// preferable long-term. DataBorrowedFunctionOwns, } impl DataFunction { fn new(vars: &VarMap, lowlevel_argument: Symbol, passed_function_argument: Param) -> Self { use DataFunction::*; let data_borrowed = !vars[&lowlevel_argument].consume; let function_borrows = passed_function_argument.borrow; match (data_borrowed, function_borrows) { (BORROWED, BORROWED) => DataBorrowedFunctionBorrows, (BORROWED, OWNED) => DataBorrowedFunctionOwns, (OWNED, BORROWED) => DataOwnedFunctionBorrows, (OWNED, OWNED) => DataOwnedFunctionOwns, } } } pub fn free_variables(stmt: &Stmt<'_>) -> MutSet { let (mut occurring, bound) = occurring_variables(stmt); for ref s in bound { occurring.remove(s); } occurring } pub fn occurring_variables(stmt: &Stmt<'_>) -> (MutSet, MutSet) { let mut stack = std::vec![stmt]; let mut result = MutSet::default(); let mut bound_variables = MutSet::default(); while let Some(stmt) = stack.pop() { use Stmt::*; match stmt { Let(symbol, expr, _, cont) => { occurring_variables_expr(expr, &mut result); result.insert(*symbol); bound_variables.insert(*symbol); stack.push(cont); } Ret(symbol) => { result.insert(*symbol); } Refcounting(modify, cont) => { let symbol = modify.get_symbol(); result.insert(symbol); stack.push(cont); } Expect { condition, remainder, .. } => { result.insert(*condition); stack.push(remainder); } Jump(_, arguments) => { result.extend(arguments.iter().copied()); } Join { parameters, body: continuation, remainder, .. } => { result.extend(parameters.iter().map(|p| p.symbol)); stack.push(continuation); stack.push(remainder); } Switch { cond_symbol, branches, default_branch, .. } => { result.insert(*cond_symbol); stack.extend(branches.iter().map(|(_, _, s)| s)); stack.push(default_branch.1); } RuntimeError(_) => {} } } (result, bound_variables) } fn occurring_variables_call(call: &crate::ir::Call<'_>, result: &mut MutSet) { // NOTE though the function name does occur, it is a static constant in the program // for liveness, it should not be included here. result.extend(call.arguments.iter().copied()); } pub fn occurring_variables_expr(expr: &Expr<'_>, result: &mut MutSet) { use Expr::*; match expr { StructAtIndex { structure: symbol, .. } => { result.insert(*symbol); } Call(call) => occurring_variables_call(call, result), Array { elems: arguments, .. } => result.extend(arguments.iter().filter_map(|e| e.to_symbol())), Tag { arguments, .. } | Struct(arguments) => { result.extend(arguments.iter().copied()); } Reuse { symbol, arguments, .. } => { result.extend(arguments.iter().copied()); result.insert(*symbol); } Reset { symbol: x, .. } => { result.insert(*x); } ExprBox { symbol } | ExprUnbox { symbol } => { result.insert(*symbol); } EmptyArray | RuntimeErrorFunction(_) | Literal(_) => {} GetTagId { structure: symbol, .. } => { result.insert(*symbol); } UnionAtIndex { structure: symbol, .. } => { result.insert(*symbol); } } } /* Insert explicit RC instructions. So, it assumes the input code does not contain `inc` nor `dec` instructions. This transformation is applied before lower level optimizations that introduce the instructions `release` and `set` */ #[derive(Clone, Debug, Copy)] struct VarInfo { reference: bool, // true if the variable may be a reference (aka pointer) at runtime persistent: bool, // true if the variable is statically known to be marked a Persistent at runtime consume: bool, // true if the variable RC must be "consumed" reset: bool, // true if the variable is the result of a Reset operation } type VarMap = MutMap; pub type LiveVarSet = MutSet; pub type JPLiveVarMap = MutMap; #[derive(Clone, Debug)] struct Context<'a> { arena: &'a Bump, vars: VarMap, jp_live_vars: JPLiveVarMap, // map: join point => live variables param_map: &'a ParamMap<'a>, } fn update_live_vars<'a>(expr: &Expr<'a>, v: &LiveVarSet) -> LiveVarSet { let mut v = v.clone(); occurring_variables_expr(expr, &mut v); v } /// `isFirstOcc xs x i = true` if `xs[i]` is the first occurrence of `xs[i]` in `xs` fn is_first_occurrence(xs: &[Symbol], i: usize) -> bool { match xs.get(i) { None => unreachable!(), Some(s) => i == xs.iter().position(|v| s == v).unwrap(), } } /// Return `n`, the number of times `x` is consumed. /// - `ys` is a sequence of instruction parameters where we search for `x`. /// - `consumeParamPred i = true` if parameter `i` is consumed. fn get_num_consumptions(x: Symbol, ys: &[Symbol], consume_param_pred: F) -> usize where F: Fn(usize) -> bool, { let mut n = 0; for (i, y) in ys.iter().enumerate() { if x == *y && consume_param_pred(i) { n += 1; } } n } /// Return true if `x` also occurs in `ys` in a position that is not consumed. /// That is, it is also passed as a borrow reference. fn is_borrow_param_help(x: Symbol, ys: &[Symbol], consume_param_pred: F) -> bool where F: Fn(usize) -> bool, { ys.iter() .enumerate() .any(|(i, y)| x == *y && !consume_param_pred(i)) } fn is_borrow_param(x: Symbol, ys: &[Symbol], ps: &[Param]) -> bool { // default to owned arguments let is_owned = |i: usize| match ps.get(i) { Some(param) => !param.borrow, None => unreachable!("or?"), }; is_borrow_param_help(x, ys, is_owned) } // We do not need to consume the projection of a variable that is not consumed fn consume_expr(m: &VarMap, e: &Expr<'_>) -> bool { match e { Expr::StructAtIndex { structure: x, .. } => match m.get(x) { Some(info) => info.consume, None => true, }, Expr::UnionAtIndex { structure: x, .. } => match m.get(x) { Some(info) => info.consume, None => true, }, _ => true, } } impl<'a> Context<'a> { pub fn new(arena: &'a Bump, param_map: &'a ParamMap<'a>) -> Self { let mut vars = MutMap::default(); for symbol in param_map.iter_symbols() { vars.insert( *symbol, VarInfo { reference: false, // assume function symbols are global constants persistent: true, // assume function symbols are global constants consume: false, // no need to consume this variable reset: false, // reset symbols cannot be passed as function arguments }, ); } Self { arena, vars, jp_live_vars: MutMap::default(), param_map, } } fn get_var_info(&self, symbol: Symbol) -> VarInfo { match self.vars.get(&symbol) { Some(info) => *info, None => { if cfg!(debug_assertions) { eprintln!( "Symbol {:?} {} has no info in self.vars", symbol, symbol, // self.vars ); } VarInfo { persistent: true, reference: false, consume: false, reset: false, } } } } fn add_inc(&self, symbol: Symbol, inc_amount: u64, stmt: &'a Stmt<'a>) -> &'a Stmt<'a> { debug_assert!(inc_amount > 0); let info = self.get_var_info(symbol); if info.persistent { // persistent values are never reference counted return stmt; } // if this symbol is never a reference, don't emit if !info.reference { return stmt; } let modify = ModifyRc::Inc(symbol, inc_amount); self.arena.alloc(Stmt::Refcounting(modify, stmt)) } fn add_dec(&self, symbol: Symbol, stmt: &'a Stmt<'a>) -> &'a Stmt<'a> { let info = self.get_var_info(symbol); if info.persistent { // persistent values are never reference counted return stmt; } // if this symbol is never a reference, don't emit if !info.reference { return stmt; } let modify = if info.reset { ModifyRc::DecRef(symbol) } else { ModifyRc::Dec(symbol) }; self.arena.alloc(Stmt::Refcounting(modify, stmt)) } fn add_inc_before_consume_all( &self, xs: &[Symbol], b: &'a Stmt<'a>, live_vars_after: &LiveVarSet, ) -> &'a Stmt<'a> { self.add_inc_before_help(xs, |_: usize| true, b, live_vars_after) } fn add_inc_before_help( &self, xs: &[Symbol], consume_param_pred: F, mut b: &'a Stmt<'a>, live_vars_after: &LiveVarSet, ) -> &'a Stmt<'a> where F: Fn(usize) -> bool + Clone, { for (i, x) in xs.iter().enumerate() { let info = self.get_var_info(*x); if !info.reference || !is_first_occurrence(xs, i) { // do nothing } else { let num_consumptions = get_num_consumptions(*x, xs, consume_param_pred.clone()); // number of times the argument is used // `x` is not a variable that must be consumed by the current procedure let need_not_consume = !info.consume; // `x` is live after executing instruction let is_live_after = live_vars_after.contains(x); // `x` is used in a position that is passed as a borrow reference let is_borrowed = is_borrow_param_help(*x, xs, consume_param_pred.clone()); let num_incs = if need_not_consume || is_live_after || is_borrowed { num_consumptions } else { num_consumptions - 1 }; if num_incs >= 1 { b = self.add_inc(*x, num_incs as u64, b) } } } b } fn add_inc_before( &self, xs: &[Symbol], ps: &[Param], b: &'a Stmt<'a>, live_vars_after: &LiveVarSet, ) -> &'a Stmt<'a> { // default to owned arguments let pred = |i: usize| match ps.get(i) { Some(param) => !param.borrow, None => unreachable!("or?"), }; self.add_inc_before_help(xs, pred, b, live_vars_after) } fn add_dec_if_needed( &self, x: Symbol, b: &'a Stmt<'a>, b_live_vars: &LiveVarSet, ) -> &'a Stmt<'a> { if self.must_consume(x) && !b_live_vars.contains(&x) { self.add_dec(x, b) } else { b } } fn must_consume(&self, x: Symbol) -> bool { let info = self.get_var_info(x); info.reference && info.consume } fn add_dec_after_application( &self, xs: &[Symbol], ps: &[Param], mut b: &'a Stmt<'a>, b_live_vars: &LiveVarSet, ) -> &'a Stmt<'a> { for (i, x) in xs.iter().enumerate() { // We must add a `dec` if `x` must be consumed, it is alive after the application, // and it has been borrowed by the application. // Remark: `x` may occur multiple times in the application (e.g., `f x y x`). // This is why we check whether it is the first occurrence. if self.must_consume(*x) && is_first_occurrence(xs, i) && is_borrow_param(*x, xs, ps) && !b_live_vars.contains(x) { b = self.add_dec(*x, b); } } b } fn add_dec_after_lowlevel( &self, xs: &[Symbol], ps: &[bool], mut b: &'a Stmt<'a>, b_live_vars: &LiveVarSet, ) -> &'a Stmt<'a> { for (i, (x, is_borrow)) in xs.iter().zip(ps.iter()).enumerate() { /* We must add a `dec` if `x` must be consumed, it is alive after the application, and it has been borrowed by the application. Remark: `x` may occur multiple times in the application (e.g., `f x y x`). This is why we check whether it is the first occurrence. */ if self.must_consume(*x) && is_first_occurrence(xs, i) && *is_borrow && !b_live_vars.contains(x) { b = self.add_dec(*x, b); } } b } #[allow(clippy::too_many_arguments)] fn visit_call<'i>( &self, codegen: &mut CodegenTools<'i>, z: Symbol, call_type: crate::ir::CallType<'a>, arguments: &'a [Symbol], l: Layout<'a>, b: &'a Stmt<'a>, b_live_vars: &LiveVarSet, ) -> &'a Stmt<'a> { use crate::ir::CallType::*; match &call_type { LowLevel { op, .. } => { let ps = crate::borrow::lowlevel_borrow_signature(self.arena, *op); let b = self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars); let v = Expr::Call(crate::ir::Call { call_type, arguments, }); &*self.arena.alloc(Stmt::Let(z, v, l, b)) } HigherOrder(lowlevel) => { self.visit_higher_order_lowlevel(codegen, z, lowlevel, arguments, l, b, b_live_vars) } Foreign { .. } => { let ps = crate::borrow::foreign_borrow_signature(self.arena, arguments.len()); let b = self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars); let v = Expr::Call(crate::ir::Call { call_type, arguments, }); &*self.arena.alloc(Stmt::Let(z, v, l, b)) } ByName { name, ret_layout, arg_layouts, .. } => { let top_level = ProcLayout::new(self.arena, arg_layouts, name.captures_niche(), **ret_layout); // get the borrow signature let ps = self .param_map .get_symbol(name.name(), top_level) .expect("function is defined"); let v = Expr::Call(crate::ir::Call { call_type, arguments, }); let b = self.add_dec_after_application(arguments, ps, b, b_live_vars); let b = self.arena.alloc(Stmt::Let(z, v, l, b)); self.add_inc_before(arguments, ps, b, b_live_vars) } } } #[allow(clippy::too_many_arguments)] fn visit_higher_order_lowlevel<'i>( &self, codegen: &mut CodegenTools<'i>, z: Symbol, lowlevel: &'a crate::ir::HigherOrderLowLevel, arguments: &'a [Symbol], l: Layout<'a>, b: &'a Stmt<'a>, b_live_vars: &LiveVarSet, ) -> &'a Stmt<'a> { use crate::low_level::HigherOrder::*; use DataFunction::*; let HigherOrderLowLevel { op, passed_function, .. } = lowlevel; macro_rules! create_call { ($borrows:expr) => { create_holl_call(self.arena, lowlevel, $borrows, arguments) }; } let function_layout = ProcLayout { arguments: passed_function.argument_layouts, result: passed_function.return_layout, captures_niche: passed_function.name.captures_niche(), }; let function_ps = match self .param_map .get_symbol(passed_function.name.name(), function_layout) { Some(function_ps) => function_ps, None => unreachable!(), }; macro_rules! handle_ownerships_post { ($stmt:expr, $args:expr) => {{ let mut stmt = $stmt; for (argument, function_ps) in $args.iter().copied() { let ownership = DataFunction::new(&self.vars, argument, function_ps); match ownership { DataOwnedFunctionOwns | DataBorrowedFunctionOwns => { // elements have been consumed, must still consume the list itself let rest = self.arena.alloc($stmt); let rc = Stmt::Refcounting(ModifyRc::DecRef(argument), rest); stmt = self.arena.alloc(rc); } DataOwnedFunctionBorrows => { // must consume list and elements let rest = self.arena.alloc($stmt); let rc = Stmt::Refcounting(ModifyRc::Dec(argument), rest); stmt = self.arena.alloc(rc); } DataBorrowedFunctionBorrows => { // list borrows, function borrows, so there is nothing to do } } } stmt }}; } macro_rules! handle_ownerships_pre { ($stmt:expr, $args:expr) => {{ let mut stmt = self.arena.alloc($stmt); for (argument, function_ps) in $args.iter().copied() { let ownership = DataFunction::new(&self.vars, argument, function_ps); match ownership { DataBorrowedFunctionOwns => { // 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); } DataOwnedFunctionOwns | DataOwnedFunctionBorrows => { // we actually own the data; nothing to do } DataBorrowedFunctionBorrows => { // list borrows, function borrows, so there is nothing to do } } } stmt }}; } // 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; let borrows = [FUNCTION, CLOSURE_DATA]; let after_arguments = &arguments[op.function_index()..]; match *op { ListMap { xs } => { let ownerships = [(xs, function_ps[0])]; let b = self.add_dec_after_lowlevel(after_arguments, &borrows, b, b_live_vars); let b = handle_ownerships_post!(b, ownerships); let v = create_call!(function_ps.get(1)); handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships) } ListMap2 { xs, ys } => { let ownerships = [(xs, function_ps[0]), (ys, function_ps[1])]; let b = self.add_dec_after_lowlevel(after_arguments, &borrows, b, b_live_vars); let b = handle_ownerships_post!(b, ownerships); let v = create_call!(function_ps.get(2)); handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships) } ListMap3 { xs, ys, zs } => { let ownerships = [ (xs, function_ps[0]), (ys, function_ps[1]), (zs, function_ps[2]), ]; let b = self.add_dec_after_lowlevel(after_arguments, &borrows, b, b_live_vars); let b = handle_ownerships_post!(b, ownerships); let v = create_call!(function_ps.get(3)); handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships) } ListMap4 { xs, ys, zs, ws } => { let ownerships = [ (xs, function_ps[0]), (ys, function_ps[1]), (zs, function_ps[2]), (ws, function_ps[3]), ]; let b = self.add_dec_after_lowlevel(after_arguments, &borrows, b, b_live_vars); let b = handle_ownerships_post!(b, ownerships); let v = create_call!(function_ps.get(3)); 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 // // we also don't check that both arguments have the same ownership characteristics let ownerships = [(xs, function_ps[0])]; let b = self.add_dec_after_lowlevel(after_arguments, &borrows, b, b_live_vars); // list-sort will sort in-place; that really changes how RC should work let b = { let ownership = DataFunction::new(&self.vars, xs, function_ps[0]); match ownership { DataOwnedFunctionOwns => { // 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( self.arena, codegen, xs, l, b.clone(), self.arena.alloc(rc), ); &*self.arena.alloc(condition_stmt) } DataOwnedFunctionBorrows => { // must consume list and elements let rc = Stmt::Refcounting(ModifyRc::Dec(xs), b); let condition_stmt = branch_on_list_uniqueness( self.arena, codegen, xs, l, b.clone(), self.arena.alloc(rc), ); &*self.arena.alloc(condition_stmt) } DataBorrowedFunctionOwns => { // elements have been consumed, must still consume the list itself let rest = self.arena.alloc(b); let rc = Stmt::Refcounting(ModifyRc::DecRef(xs), rest); &*self.arena.alloc(rc) } DataBorrowedFunctionBorrows => { // list borrows, function borrows, so there is nothing to do b } } }; let v = create_call!(function_ps.get(2)); handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships) } } } #[allow(clippy::many_single_char_names)] fn visit_variable_declaration<'i>( &self, codegen: &mut CodegenTools<'i>, z: Symbol, v: Expr<'a>, l: Layout<'a>, b: &'a Stmt<'a>, b_live_vars: &LiveVarSet, ) -> (&'a Stmt<'a>, LiveVarSet) { use Expr::*; let mut live_vars = update_live_vars(&v, b_live_vars); live_vars.remove(&z); let new_b = match v { Reuse { arguments: ys, .. } | Tag { arguments: ys, .. } | Struct(ys) => self .add_inc_before_consume_all( ys, self.arena.alloc(Stmt::Let(z, v, l, b)), b_live_vars, ), Array { elems, .. } => { let ys = Vec::from_iter_in(elems.iter().filter_map(|e| e.to_symbol()), self.arena); self.add_inc_before_consume_all( &ys, self.arena.alloc(Stmt::Let(z, v, l, b)), b_live_vars, ) } Call(crate::ir::Call { call_type, arguments, }) => self.visit_call(codegen, z, call_type, arguments, l, b, b_live_vars), StructAtIndex { structure: x, .. } => { let b = self.add_dec_if_needed(x, b, b_live_vars); let info_x = self.get_var_info(x); let b = if info_x.consume { self.add_inc(z, 1, b) } else { b }; self.arena.alloc(Stmt::Let(z, v, l, b)) } GetTagId { structure: x, .. } => { let b = self.add_dec_if_needed(x, b, b_live_vars); let info_x = self.get_var_info(x); let b = if info_x.consume { self.add_inc(z, 1, b) } else { b }; self.arena.alloc(Stmt::Let(z, v, l, b)) } UnionAtIndex { structure: x, .. } => { let b = self.add_dec_if_needed(x, b, b_live_vars); let info_x = self.get_var_info(x); let b = if info_x.consume { self.add_inc(z, 1, b) } else { b }; self.arena.alloc(Stmt::Let(z, v, l, b)) } ExprBox { symbol: x } => { // mimics Tag self.add_inc_before_consume_all( &[x], self.arena.alloc(Stmt::Let(z, v, l, b)), b_live_vars, ) } ExprUnbox { symbol: x } => { // mimics UnionAtIndex let b = self.add_dec_if_needed(x, b, b_live_vars); let info_x = self.get_var_info(x); let b = if info_x.consume { self.add_inc(z, 1, b) } else { b }; self.arena.alloc(Stmt::Let(z, v, l, b)) } EmptyArray | Literal(_) | Reset { .. } | RuntimeErrorFunction(_) => { // EmptyArray is always stack-allocated // function pointers are persistent self.arena.alloc(Stmt::Let(z, v, l, b)) } }; (new_b, live_vars) } 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? let persistent = false; // must this value be consumed? let consume = consume_expr(&self.vars, expr); let reset = matches!(expr, Expr::Reset { .. }); self.update_var_info_help(symbol, layout, persistent, consume, reset) } fn update_var_info_help( &self, symbol: Symbol, layout: &Layout<'a>, persistent: bool, consume: bool, reset: bool, ) -> Self { // should we perform incs and decs on this value? let reference = layout.contains_refcounted(); let info = VarInfo { reference, persistent, consume, reset, }; let mut ctx = self.clone(); ctx.vars.insert(symbol, info); ctx } fn update_var_info_with_params(&self, ps: &[Param]) -> Self { let mut ctx = self.clone(); for p in ps.iter() { let info = VarInfo { reference: p.layout.contains_refcounted(), consume: !p.borrow, persistent: false, reset: false, }; ctx.vars.insert(p.symbol, info); } ctx } // Add `dec` instructions for parameters that are // // - references // - not alive in `b` // - not borrow. // // That is, we must make sure these parameters are consumed. fn add_dec_for_dead_params( &self, ps: &[Param<'a>], mut b: &'a Stmt<'a>, b_live_vars: &LiveVarSet, ) -> &'a Stmt<'a> { for p in ps.iter() { if !p.borrow && p.layout.contains_refcounted() && !b_live_vars.contains(&p.symbol) { b = self.add_dec(p.symbol, b) } } b } fn add_dec_for_alt( &self, case_live_vars: &LiveVarSet, alt_live_vars: &LiveVarSet, mut b: &'a Stmt<'a>, ) -> &'a Stmt<'a> { for x in case_live_vars.iter() { if !alt_live_vars.contains(x) && self.must_consume(*x) { b = self.add_dec(*x, b); } } b } fn visit_stmt<'i>( &self, codegen: &mut CodegenTools<'i>, stmt: &'a Stmt<'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 mut cont = stmt; let mut triples = Vec::new_in(self.arena); while let Stmt::Let(symbol, expr, layout, new_cont) = cont { triples.push((symbol, expr, layout)); cont = new_cont; } if !triples.is_empty() { let mut ctx = self.clone(); for (symbol, expr, layout) in triples.iter() { ctx = ctx.update_var_info(**symbol, layout, expr); } let (mut b, mut b_live_vars) = ctx.visit_stmt(codegen, cont); for (symbol, expr, layout) in triples.into_iter().rev() { let pair = ctx.visit_variable_declaration( codegen, *symbol, (*expr).clone(), *layout, b, &b_live_vars, ); b = pair.0; b_live_vars = pair.1; } return (b, b_live_vars); } } match stmt { Let(symbol, expr, layout, cont) => { let ctx = self.update_var_info(*symbol, layout, expr); let (b, b_live_vars) = ctx.visit_stmt(codegen, cont); ctx.visit_variable_declaration( codegen, *symbol, expr.clone(), *layout, b, &b_live_vars, ) } Join { id: j, parameters: _, remainder: b, body: v, } => { // get the parameters with borrow signature let xs = self.param_map.get_join_point(*j); let (v, v_live_vars) = { let ctx = self.update_var_info_with_params(xs); ctx.visit_stmt(codegen, v) }; let mut ctx = self.clone(); let v = ctx.add_dec_for_dead_params(xs, v, &v_live_vars); update_jp_live_vars(*j, xs, v, &mut ctx.jp_live_vars); let (b, b_live_vars) = ctx.visit_stmt(codegen, b); ( ctx.arena.alloc(Join { id: *j, parameters: xs, remainder: b, body: v, }), b_live_vars, ) } Ret(x) => { let info = self.get_var_info(*x); let mut live_vars = MutSet::default(); live_vars.insert(*x); if info.reference && !info.consume { (self.add_inc(*x, 1, stmt), live_vars) } else { (stmt, live_vars) } } Jump(j, xs) => { let empty = MutSet::default(); let j_live_vars = match self.jp_live_vars.get(j) { Some(vars) => vars, None => &empty, }; // TODO use borrow signature here? let ps = self.param_map.get_join_point(*j); let b = self.add_inc_before(xs, ps, stmt, j_live_vars); let b_live_vars = collect_stmt(b, &self.jp_live_vars, MutSet::default()); (b, b_live_vars) } Switch { cond_symbol, cond_layout, branches, default_branch, ret_layout, } => { let case_live_vars = collect_stmt(stmt, &self.jp_live_vars, MutSet::default()); let branches = Vec::from_iter_in( branches.iter().map(|(label, info, branch)| { // TODO should we use ctor info like Lean? let ctx = self.clone(); let (b, alt_live_vars) = ctx.visit_stmt(codegen, branch); let b = ctx.add_dec_for_alt(&case_live_vars, &alt_live_vars, b); (*label, info.clone(), b.clone()) }), self.arena, ) .into_bump_slice(); let default_branch = { // TODO should we use ctor info like Lean? let ctx = self.clone(); let (b, alt_live_vars) = ctx.visit_stmt(codegen, default_branch.1); ( default_branch.0.clone(), ctx.add_dec_for_alt(&case_live_vars, &alt_live_vars, b), ) }; let switch = self.arena.alloc(Switch { cond_symbol: *cond_symbol, branches, default_branch, cond_layout: *cond_layout, ret_layout: *ret_layout, }); (switch, case_live_vars) } Expect { remainder, condition, region, lookups, layouts, } => { let (b, b_live_vars) = self.visit_stmt(codegen, remainder); let expect = self.arena.alloc(Stmt::Expect { condition: *condition, region: *region, lookups, layouts, remainder: b, }); (expect, b_live_vars) } RuntimeError(_) | Refcounting(_, _) => (stmt, MutSet::default()), } } } fn branch_on_list_uniqueness<'a, 'i>( arena: &'a Bump, codegen: &mut CodegenTools<'i>, list_symbol: Symbol, return_layout: Layout<'a>, then_branch_stmt: Stmt<'a>, else_branch_stmt: &'a Stmt<'a>, ) -> Stmt<'a> { let condition_symbol = Symbol::new(codegen.home, codegen.ident_ids.add_str("listIsUnique")); let when_stmt = Stmt::if_then_else( arena, condition_symbol, return_layout, then_branch_stmt, else_branch_stmt, ); let stmt = arena.alloc(when_stmt); // define the condition let condition_call_type = CallType::LowLevel { op: roc_module::low_level::LowLevel::ListIsUnique, update_mode: codegen.update_mode_ids.next_id(), }; let condition_call = crate::ir::Call { call_type: condition_call_type, arguments: arena.alloc([list_symbol]), }; Stmt::Let( condition_symbol, Expr::Call(condition_call), Layout::bool(), stmt, ) } fn create_holl_call<'a>( arena: &'a Bump, holl: &'a crate::ir::HigherOrderLowLevel, param: Option<&Param>, arguments: &'a [Symbol], ) -> Expr<'a> { let call = crate::ir::Call { call_type: if let Some(OWNED) = param.map(|p| p.borrow) { let mut passed_function = holl.passed_function; passed_function.owns_captured_environment = true; let higher_order = HigherOrderLowLevel { op: holl.op, closure_env_layout: holl.closure_env_layout, update_mode: holl.update_mode, passed_function, }; CallType::HigherOrder(arena.alloc(higher_order)) } else { debug_assert!(!holl.passed_function.owns_captured_environment); CallType::HigherOrder(holl) }, arguments, }; Expr::Call(call) } pub fn collect_stmt( stmt: &Stmt<'_>, jp_live_vars: &JPLiveVarMap, mut vars: LiveVarSet, ) -> LiveVarSet { use Stmt::*; match stmt { Let(symbol, expr, _, cont) => { vars = collect_stmt(cont, jp_live_vars, vars); vars.remove(symbol); let mut result = MutSet::default(); occurring_variables_expr(expr, &mut result); vars.extend(result); vars } Ret(symbol) => { vars.insert(*symbol); vars } Refcounting(modify, cont) => { let symbol = modify.get_symbol(); vars.insert(symbol); collect_stmt(cont, jp_live_vars, vars) } Expect { condition, remainder, .. } => { vars.insert(*condition); collect_stmt(remainder, jp_live_vars, vars) } Join { id: j, parameters, remainder: b, body: v, } => { let mut j_live_vars = collect_stmt(v, jp_live_vars, MutSet::default()); for param in parameters.iter() { j_live_vars.remove(¶m.symbol); } let mut jp_live_vars = jp_live_vars.clone(); jp_live_vars.insert(*j, j_live_vars); collect_stmt(b, &jp_live_vars, vars) } Jump(id, arguments) => { vars.extend(arguments.iter().copied()); // NOTE deviation from Lean // we fall through when no join point is available if let Some(jvars) = jp_live_vars.get(id) { vars.extend(jvars); } vars } Switch { cond_symbol, branches, default_branch, .. } => { vars.insert(*cond_symbol); for (_, _info, branch) in branches.iter() { vars.extend(collect_stmt(branch, jp_live_vars, vars.clone())); } vars.extend(collect_stmt(default_branch.1, jp_live_vars, vars.clone())); vars } RuntimeError(_) => vars, } } fn update_jp_live_vars(j: JoinPointId, ys: &[Param], v: &Stmt<'_>, m: &mut JPLiveVarMap) { let j_live_vars = MutSet::default(); let mut j_live_vars = collect_stmt(v, m, j_live_vars); for param in ys { j_live_vars.remove(¶m.symbol); } m.insert(j, j_live_vars); } struct CodegenTools<'i> { home: ModuleId, ident_ids: &'i mut IdentIds, update_mode_ids: &'i mut UpdateModeIds, } pub fn visit_procs<'a, 'i>( arena: &'a Bump, home: ModuleId, ident_ids: &'i mut IdentIds, update_mode_ids: &'i mut UpdateModeIds, param_map: &'a ParamMap<'a>, procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) { let ctx = Context::new(arena, param_map); let mut codegen = CodegenTools { home, ident_ids, update_mode_ids, }; for (key, proc) in procs.iter_mut() { visit_proc(arena, &mut codegen, param_map, &ctx, proc, key.1); } } fn visit_proc<'a, 'i>( arena: &'a Bump, codegen: &mut CodegenTools<'i>, param_map: &'a ParamMap<'a>, ctx: &Context<'a>, proc: &mut Proc<'a>, layout: ProcLayout<'a>, ) { let params = match param_map.get_symbol(proc.name.name(), layout) { Some(slice) => slice, None => Vec::from_iter_in( proc.args.iter().cloned().map(|(layout, symbol)| Param { symbol, borrow: false, layout, }), arena, ) .into_bump_slice(), }; let stmt = arena.alloc(proc.body.clone()); let ctx = ctx.update_var_info_with_params(params); let (b, b_live_vars) = ctx.visit_stmt(codegen, stmt); let b = ctx.add_dec_for_dead_params(params, b, &b_live_vars); proc.body = b.clone(); }