diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index acd839f9f2..5aace830d3 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -256,7 +256,6 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( match expr { Literal(literal) => build_exp_literal(env, literal), - Alias(symbol) => load_symbol(env, scope, symbol), RunLowLevel(op, symbols) => run_low_level(env, scope, parent, *op, symbols), FunctionCall { diff --git a/compiler/mono/src/inc_dec.rs b/compiler/mono/src/inc_dec.rs index 639e3e2727..41ecfb537e 100644 --- a/compiler/mono/src/inc_dec.rs +++ b/compiler/mono/src/inc_dec.rs @@ -93,7 +93,6 @@ pub fn occuring_variables_expr(expr: &Expr<'_>, result: &mut MutSet) { match expr { FunctionPointer(symbol, _) - | Alias(symbol) | AccessAtIndex { structure: symbol, .. } => { @@ -239,6 +238,11 @@ impl<'a> Context<'a> { return stmt; } + // if this symbol is never a reference, don't emit + if !info.reference { + return stmt; + } + self.arena.alloc(Stmt::Inc(symbol, stmt)) } @@ -250,6 +254,11 @@ impl<'a> Context<'a> { return stmt; } + // if this symbol is never a reference, don't emit + if !info.reference { + return stmt; + } + self.arena.alloc(Stmt::Dec(symbol, stmt)) } @@ -424,13 +433,8 @@ impl<'a> Context<'a> { ), AccessAtIndex { structure: x, .. } => { let b = self.add_dec_if_needed(x, b, b_live_vars); - // NOTE deviation from Lean. I think lean assumes all structure elements live on - // the heap. Therefore any access to a Tag/Struct element must increment its - // refcount. But in roc, structure elements can be unboxed. let info_x = self.get_var_info(x); - // let info_z = self.get_var_info(z); let b = if info_x.consume { - println!("inc on {}", z); self.add_inc(z, b) } else { b @@ -489,7 +493,6 @@ impl<'a> Context<'a> { self.arena.alloc(Stmt::Let(z, v, l, b)) } - Alias(_) => unreachable!("well, it should be unreachable!"), EmptyArray | FunctionPointer(_, _) | Literal(_) | RuntimeErrorFunction(_) => { // EmptyArray is always stack-allocated @@ -734,7 +737,8 @@ impl<'a> Context<'a> { (switch, case_live_vars) } - _ => todo!(), + RuntimeError(_) | Inc(_,_) | Dec(_,_) => (stmt, MutSet::default()), + } } } diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index b01919c5a9..30ad57a505 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -404,10 +404,6 @@ pub enum CallType { pub enum Expr<'a> { Literal(Literal<'a>), - /// A symbol will alias this symbol - /// in the long term we should get rid of this using copy propagation - Alias(Symbol), - // Functions FunctionPointer(Symbol, Layout<'a>), FunctionCall { @@ -490,7 +486,6 @@ impl<'a> Expr<'a> { match self { Literal(lit) => lit.to_doc(alloc), - Alias(symbol) => alloc.text("alias ").append(symbol_to_doc(alloc, *symbol)), FunctionPointer(symbol, _) => symbol_to_doc(alloc, *symbol), @@ -1049,7 +1044,12 @@ pub fn with_hole<'a>( LetNonRec(def, cont, _, _) => { // WRONG! this is introduces new control flow, and should call `from_can` again if let roc_can::pattern::Pattern::Identifier(symbol) = def.loc_pattern.value { - let stmt = with_hole(env, cont.value, procs, layout_cache, assigned, hole); + let mut stmt = with_hole(env, cont.value, procs, layout_cache, assigned, hole); + + // this is an alias of a variable + if let roc_can::expr::Expr::Var(original) = def.loc_expr.value { + substitute_in_exprs(env.arena, &mut stmt, symbol, original); + } with_hole( env, @@ -1060,7 +1060,51 @@ pub fn with_hole<'a>( env.arena.alloc(stmt), ) } else { - todo!() + // this may be a destructure pattern + let mono_pattern = from_can_pattern(env, layout_cache, &def.loc_pattern.value); + + if let Pattern::Identifier(symbol) = mono_pattern { + let hole = env + .arena + .alloc(from_can(env, cont.value, procs, layout_cache)); + with_hole(env, def.loc_expr.value, procs, layout_cache, symbol, hole) + } else { + let context = crate::exhaustive::Context::BadDestruct; + match crate::exhaustive::check( + def.loc_pattern.region, + &[( + Located::at(def.loc_pattern.region, mono_pattern.clone()), + crate::exhaustive::Guard::NoGuard, + )], + context, + ) { + Ok(_) => {} + Err(errors) => { + for error in errors { + env.problems.push(MonoProblem::PatternProblem(error)) + } + } // TODO make all variables bound in the pattern evaluate to a runtime error + // return Stmt::RuntimeError("TODO non-exhaustive pattern"); + } + + // convert the continuation + let mut stmt = from_can(env, cont.value, procs, layout_cache); + + let outer_symbol = env.unique_symbol(); + stmt = + store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt) + .unwrap(); + + // convert the def body, store in outer_symbol + with_hole( + env, + def.loc_expr.value, + procs, + layout_cache, + outer_symbol, + env.arena.alloc(stmt), + ) + } } } Var(symbol) => { @@ -1840,24 +1884,12 @@ pub fn from_can<'a>( // return Stmt::RuntimeError("TODO non-exhaustive pattern"); } - let layout = layout_cache - .from_var(env.arena, def.expr_var, env.subs) - .expect("invalid layout"); - // convert the continuation let mut stmt = from_can(env, cont.value, procs, layout_cache); let outer_symbol = env.unique_symbol(); - stmt = store_pattern( - env, - procs, - layout_cache, - &mono_pattern, - outer_symbol, - layout, - stmt, - ) - .unwrap(); + stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt) + .unwrap(); // convert the def body, store in outer_symbol with_hole( @@ -2012,15 +2044,7 @@ fn from_can_when<'a>( let guard_stmt = with_hole(env, loc_expr.value, procs, layout_cache, symbol, jump); - match store_pattern( - env, - procs, - layout_cache, - &pattern, - cond_symbol, - cond_layout.clone(), - guard_stmt, - ) { + match store_pattern(env, procs, layout_cache, &pattern, cond_symbol, guard_stmt) { Ok(new_guard_stmt) => ( pattern, Guard::Guard { @@ -2037,15 +2061,7 @@ fn from_can_when<'a>( ), } } else { - match store_pattern( - env, - procs, - layout_cache, - &pattern, - cond_symbol, - cond_layout.clone(), - branch_stmt, - ) { + match store_pattern(env, procs, layout_cache, &pattern, cond_symbol, branch_stmt) { Ok(new_branch_stmt) => (pattern, Guard::NoGuard, new_branch_stmt), Err(msg) => ( Pattern::Underscore, @@ -2068,6 +2084,363 @@ fn from_can_when<'a>( ) } +fn substitute(substitutions: &MutMap, s: Symbol) -> Option { + match substitutions.get(&s) { + Some(new) => { + debug_assert!(!substitutions.contains_key(new)); + Some(*new) + } + None => None, + } +} + +fn substitute_in_exprs<'a>(arena: &'a Bump, stmt: &mut Stmt<'a>, from: Symbol, to: Symbol) { + let mut subs = MutMap::default(); + subs.insert(from, to); + + // TODO clean this up + let ref_stmt = arena.alloc(stmt.clone()); + if let Some(new) = substitute_in_stmt_help(arena, ref_stmt, &subs) { + *stmt = new.clone(); + } +} + +fn substitute_in_stmt_help<'a>( + arena: &'a Bump, + stmt: &'a Stmt<'a>, + subs: &MutMap, +) -> Option<&'a Stmt<'a>> { + use Stmt::*; + + match stmt { + Let(symbol, expr, layout, cont) => { + let opt_cont = substitute_in_stmt_help(arena, cont, subs); + let opt_expr = substitute_in_expr(arena, expr, subs); + + if opt_expr.is_some() || opt_cont.is_some() { + let cont = opt_cont.unwrap_or(cont); + let expr = opt_expr.unwrap_or_else(|| expr.clone()); + + Some(arena.alloc(Let(*symbol, expr, layout.clone(), cont))) + } else { + None + } + } + Join { + id, + parameters, + remainder, + continuation, + } => { + let opt_remainder = substitute_in_stmt_help(arena, remainder, subs); + let opt_continuation = substitute_in_stmt_help(arena, continuation, subs); + + if opt_remainder.is_some() || opt_continuation.is_some() { + let remainder = opt_remainder.unwrap_or(remainder); + let continuation = opt_continuation.unwrap_or_else(|| *continuation); + + Some(arena.alloc(Join { + id: *id, + parameters, + remainder, + continuation, + })) + } else { + None + } + } + Cond { + cond_symbol, + cond_layout, + branching_symbol, + branching_layout, + pass, + fail, + ret_layout, + } => { + let opt_pass = substitute_in_stmt_help(arena, pass, subs); + let opt_fail = substitute_in_stmt_help(arena, fail, subs); + + if opt_pass.is_some() || opt_fail.is_some() { + let pass = opt_pass.unwrap_or(pass); + let fail = opt_fail.unwrap_or_else(|| *fail); + + Some(arena.alloc(Cond { + cond_symbol: *cond_symbol, + cond_layout: cond_layout.clone(), + branching_symbol: *branching_symbol, + branching_layout: branching_layout.clone(), + pass, + fail, + ret_layout: ret_layout.clone(), + })) + } else { + None + } + } + Switch { + cond_symbol, + cond_layout, + branches, + default_branch, + ret_layout, + } => { + let opt_default = substitute_in_stmt_help(arena, default_branch, subs); + + let mut did_change = false; + + let opt_branches = Vec::from_iter_in( + branches.iter().map(|(label, branch)| { + match substitute_in_stmt_help(arena, branch, subs) { + None => None, + Some(branch) => { + did_change = true; + Some((*label, branch.clone())) + } + } + }), + arena, + ); + + if opt_default.is_some() || did_change { + let default_branch = opt_default.unwrap_or(default_branch); + + let branches = if did_change { + let new = Vec::from_iter_in( + opt_branches.into_iter().zip(branches.iter()).map( + |(opt_branch, branch)| match opt_branch { + None => branch.clone(), + Some(new_branch) => new_branch, + }, + ), + arena, + ); + + new.into_bump_slice() + } else { + branches + }; + + Some(arena.alloc(Switch { + cond_symbol: *cond_symbol, + cond_layout: cond_layout.clone(), + default_branch, + branches, + ret_layout: ret_layout.clone(), + })) + } else { + None + } + } + Ret(s) => match substitute(subs, *s) { + Some(s) => Some(arena.alloc(Ret(s))), + None => None, + }, + Inc(symbol, cont) => match substitute_in_stmt_help(arena, cont, subs) { + Some(cont) => Some(arena.alloc(Inc(*symbol, cont))), + None => None, + }, + Dec(symbol, cont) => match substitute_in_stmt_help(arena, cont, subs) { + Some(cont) => Some(arena.alloc(Dec(*symbol, cont))), + None => None, + }, + + Jump(id, args) => { + let mut did_change = false; + let new_args = Vec::from_iter_in( + args.iter().map(|s| match substitute(subs, *s) { + None => *s, + Some(s) => { + did_change = true; + s + } + }), + arena, + ); + + if did_change { + let args = new_args.into_bump_slice(); + + Some(arena.alloc(Jump(*id, args))) + } else { + None + } + } + + RuntimeError(_) => None, + } +} + +fn substitute_in_expr<'a>( + arena: &'a Bump, + expr: &'a Expr<'a>, + subs: &MutMap, +) -> Option> { + use Expr::*; + + match expr { + Literal(_) | FunctionPointer(_, _) | EmptyArray | RuntimeErrorFunction(_) => None, + + FunctionCall { + call_type, + args, + arg_layouts, + layout, + } => { + let opt_call_type = match call_type { + CallType::ByName(s) => substitute(subs, *s).map(CallType::ByName), + CallType::ByPointer(s) => substitute(subs, *s).map(CallType::ByPointer), + }; + + let mut did_change = false; + let new_args = Vec::from_iter_in( + args.iter().map(|s| match substitute(subs, *s) { + None => *s, + Some(s) => { + did_change = true; + s + } + }), + arena, + ); + + if did_change || opt_call_type.is_some() { + let call_type = opt_call_type.unwrap_or(*call_type); + + let args = new_args.into_bump_slice(); + + Some(FunctionCall { + call_type, + args, + arg_layouts: *arg_layouts, + layout: layout.clone(), + }) + } else { + None + } + } + RunLowLevel(op, args) => { + let mut did_change = false; + let new_args = Vec::from_iter_in( + args.iter().map(|s| match substitute(subs, *s) { + None => *s, + Some(s) => { + did_change = true; + s + } + }), + arena, + ); + + if did_change { + let args = new_args.into_bump_slice(); + + Some(RunLowLevel(*op, args)) + } else { + None + } + } + + Tag { + tag_layout, + tag_name, + tag_id, + union_size, + arguments: args, + } => { + let mut did_change = false; + let new_args = Vec::from_iter_in( + args.iter().map(|s| match substitute(subs, *s) { + None => *s, + Some(s) => { + did_change = true; + s + } + }), + arena, + ); + + if did_change { + let arguments = new_args.into_bump_slice(); + + Some(Tag { + tag_layout: tag_layout.clone(), + tag_name: tag_name.clone(), + tag_id: *tag_id, + union_size: *union_size, + arguments, + }) + } else { + None + } + } + Struct(args) => { + let mut did_change = false; + let new_args = Vec::from_iter_in( + args.iter().map(|s| match substitute(subs, *s) { + None => *s, + Some(s) => { + did_change = true; + s + } + }), + arena, + ); + + if did_change { + let args = new_args.into_bump_slice(); + + Some(Struct(args)) + } else { + None + } + } + + Array { + elems: args, + elem_layout, + } => { + let mut did_change = false; + let new_args = Vec::from_iter_in( + args.iter().map(|s| match substitute(subs, *s) { + None => *s, + Some(s) => { + did_change = true; + s + } + }), + arena, + ); + + if did_change { + let args = new_args.into_bump_slice(); + + Some(Array { + elem_layout: elem_layout.clone(), + elems: args, + }) + } else { + None + } + } + + AccessAtIndex { + index, + structure, + field_layouts, + is_unwrapped, + } => match substitute(subs, *structure) { + Some(structure) => Some(AccessAtIndex { + index: *index, + field_layouts: *field_layouts, + is_unwrapped: *is_unwrapped, + structure, + }), + None => None, + }, + } +} + #[allow(clippy::too_many_arguments)] fn store_pattern<'a>( env: &mut Env<'a, '_>, @@ -2075,15 +2448,13 @@ fn store_pattern<'a>( layout_cache: &mut LayoutCache<'a>, can_pat: &Pattern<'a>, outer_symbol: Symbol, - layout: Layout<'a>, mut stmt: Stmt<'a>, ) -> Result, &'a str> { use Pattern::*; match can_pat { Identifier(symbol) => { - let expr = Expr::Alias(outer_symbol); - stmt = Stmt::Let(*symbol, expr, layout, env.arena.alloc(stmt)); + substitute_in_exprs(env.arena, &mut stmt, *symbol, outer_symbol); } Underscore => { // do nothing @@ -2134,15 +2505,7 @@ fn store_pattern<'a>( let symbol = env.unique_symbol(); // first recurse, continuing to unpack symbol - stmt = store_pattern( - env, - procs, - layout_cache, - argument, - symbol, - arg_layout.clone(), - stmt, - )?; + stmt = store_pattern(env, procs, layout_cache, argument, symbol, stmt)?; // then store the symbol stmt = Stmt::Let(symbol, load, arg_layout.clone(), env.arena.alloc(stmt)); @@ -2244,15 +2607,7 @@ fn store_record_destruct<'a>( _ => { let symbol = env.unique_symbol(); - stmt = store_pattern( - env, - procs, - layout_cache, - guard_pattern, - symbol, - destruct.layout.clone(), - stmt, - )?; + stmt = store_pattern(env, procs, layout_cache, guard_pattern, symbol, stmt)?; stmt = Stmt::Let(symbol, load, destruct.layout.clone(), env.arena.alloc(stmt)); } diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 54ba7541df..e0255fb566 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -125,14 +125,14 @@ mod test_mono { "#, indoc!( r#" - let Test.2 = true; - if Test.2 then - let Test.0 = 1i64; - jump Test.1 Test.0; + let Test.3 = true; + if Test.3 then + let Test.1 = 1i64; + jump Test.2 Test.1; else - let Test.0 = 2i64; - jump Test.1 Test.0; - joinpoint Test.1 Test.0: + let Test.1 = 2i64; + jump Test.2 Test.1; + joinpoint Test.2 Test.0: ret Test.0; "# ), @@ -310,19 +310,19 @@ mod test_mono { indoc!( r#" procedure Num.32 (#Attr.2, #Attr.3): - let Test.20 = 0i64; - let Test.17 = lowlevel NotEq #Attr.3 Test.20; - if Test.17 then - let Test.18 = 1i64; - let Test.19 = lowlevel NumDivUnchecked #Attr.2 #Attr.3; - let Test.13 = Ok Test.18 Test.19; - jump Test.14 Test.13; + let Test.21 = 0i64; + let Test.18 = lowlevel NotEq #Attr.3 Test.21; + if Test.18 then + let Test.19 = 1i64; + let Test.20 = lowlevel NumDivUnchecked #Attr.2 #Attr.3; + let Test.14 = Ok Test.19 Test.20; + jump Test.15 Test.14; else - let Test.15 = 0i64; - let Test.16 = Struct {}; - let Test.13 = Err Test.15 Test.16; - jump Test.14 Test.13; - joinpoint Test.14 Test.13: + let Test.16 = 0i64; + let Test.17 = Struct {}; + let Test.14 = Err Test.16 Test.17; + jump Test.15 Test.14; + joinpoint Test.15 Test.13: ret Test.13; let Test.11 = 1000i64; @@ -440,14 +440,14 @@ mod test_mono { "#, indoc!( r#" - let Test.3 = true; - if Test.3 then - let Test.0 = 1i64; - jump Test.2 Test.0; + let Test.4 = true; + if Test.4 then + let Test.2 = 1i64; + jump Test.3 Test.2; else - let Test.0 = 2i64; - jump Test.2 Test.0; - joinpoint Test.2 Test.0: + let Test.2 = 2i64; + jump Test.3 Test.2; + joinpoint Test.3 Test.0: ret Test.0; "# ), @@ -603,21 +603,52 @@ mod test_mono { } #[test] - fn list_push() { + fn list_append_closure() { compiles_to_ir( r#" - List.push [1] 2 + myFunction = \l -> List.append l 42 + + myFunction [ 1, 2 ] + "#, + indoc!( + r#" + procedure Test.0 (Test.2): + let Test.6 = 42i64; + let Test.5 = CallByName List.5 Test.2 Test.6; + ret Test.5; + + procedure List.5 (#Attr.2, #Attr.3): + let Test.7 = lowlevel ListAppend #Attr.2 #Attr.3; + ret Test.7; + + let Test.8 = 1i64; + let Test.9 = 2i64; + let Test.4 = Array [Test.8, Test.9]; + let Test.3 = CallByName Test.0 Test.4; + dec Test.4; + ret Test.3; + "# + ), + ) + } + + #[test] + fn list_append() { + compiles_to_ir( + r#" + List.append [1] 2 "#, indoc!( r#" procedure List.5 (#Attr.2, #Attr.3): - let Test.3 = lowlevel ListPush #Attr.2 #Attr.3; + let Test.3 = lowlevel ListAppend #Attr.2 #Attr.3; ret Test.3; let Test.4 = 1i64; let Test.1 = Array [Test.4]; let Test.2 = 2i64; let Test.0 = CallByName List.5 Test.1 Test.2; + dec Test.1; ret Test.0; "# ), @@ -635,15 +666,26 @@ mod test_mono { "#, indoc!( r#" - procedure List.5 (#Attr.2, #Attr.3): - let Test.3 = lowlevel ListPush #Attr.2 #Attr.3; - ret Test.3; + procedure Num.14 (#Attr.2, #Attr.3): + let Test.5 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Test.5; - let Test.4 = 1i64; - let Test.1 = Array [Test.4]; - let Test.2 = 2i64; - let Test.0 = CallByName List.5 Test.1 Test.2; - ret Test.0; + procedure List.7 (#Attr.2): + let Test.6 = lowlevel ListLen #Attr.2; + ret Test.6; + + let Test.10 = 1f64; + let Test.1 = Array [Test.10]; + let Test.7 = 1i64; + let Test.8 = 2i64; + let Test.9 = 3i64; + let Test.0 = Array [Test.7, Test.8, Test.9]; + let Test.3 = CallByName List.7 Test.0; + dec Test.0; + let Test.4 = CallByName List.7 Test.1; + dec Test.1; + let Test.2 = CallByName Num.14 Test.3 Test.4; + ret Test.2; "# ), ) @@ -806,8 +848,30 @@ mod test_mono { let Test.5 = 3.14f64; let Test.3 = Struct {Test.4, Test.5}; let Test.0 = Index 0 Test.3; + ret Test.0; + "# + ), + ) + } + + #[test] + fn let_with_record_pattern_list() { + compiles_to_ir( + r#" + { x } = { x: [ 1, 3, 4 ], y: 3.14 } + + x + "#, + indoc!( + r#" + let Test.6 = 1i64; + let Test.7 = 3i64; + let Test.8 = 4i64; + let Test.4 = Array [Test.6, Test.7, Test.8]; + let Test.5 = 3.14f64; + let Test.3 = Struct {Test.4, Test.5}; + let Test.0 = Index 0 Test.3; inc Test.0; - dec Test.3; ret Test.0; "# ), @@ -832,9 +896,8 @@ mod test_mono { let Test.2 = 10i64; let Test.11 = true; - let Test.0 = alias Test.2; let Test.7 = 5i64; - let Test.6 = CallByName Bool.5 Test.0 Test.7; + let Test.6 = CallByName Bool.5 Test.2 Test.7; jump Test.5 Test.6; joinpoint Test.5 Test.12: let Test.10 = lowlevel And Test.12 Test.11; @@ -852,27 +915,66 @@ mod test_mono { } #[test] - fn beans_example_1() { + fn alias_variable() { compiles_to_ir( indoc!( r#" - y = 10 + x = 5 + y = x - z = Num.add y y - - z + 3 "# ), indoc!( r#" - procedure Num.14 (#Attr.2, #Attr.3): - let Test.3 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.3; + let Test.0 = 5i64; + ret Test.0; + "# + ), + ); - let Test.0 = 10i64; - inc Test.0; - let Test.1 = CallByName Num.14 Test.0 Test.0; - ret Test.1; + compiles_to_ir( + indoc!( + r#" + x = 5 + y = x + + y + "# + ), + indoc!( + r#" + let Test.0 = 5i64; + ret Test.0; + "# + ), + ) + } + + #[test] + fn branch_store_variable() { + compiles_to_ir( + indoc!( + r#" + when 0 is + 1 -> 12 + a -> a + "# + ), + indoc!( + r#" + let Test.2 = 0i64; + let Test.7 = true; + let Test.8 = 1i64; + let Test.9 = lowlevel Eq Test.8 Test.2; + let Test.6 = lowlevel And Test.9 Test.7; + if Test.6 then + let Test.4 = 12i64; + jump Test.3 Test.4; + else + jump Test.3 Test.2; + joinpoint Test.3 Test.1: + ret Test.1; "# ), )