From 6b9b64274bfce603a5b4d8cd1df2c281c27c9a1f Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 9 May 2022 16:23:00 -0400 Subject: [PATCH 1/5] Lift translation of lets --- compiler/mono/src/ir.rs | 570 ++++++++++++++++++++-------------------- 1 file changed, 289 insertions(+), 281 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index afeb17efc4..9136861c05 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -15,7 +15,6 @@ use roc_debug_flags::{ dbg_do, ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE, ROC_PRINT_IR_AFTER_SPECIALIZATION, }; -use roc_error_macros::internal_error; use roc_exhaustive::{Ctor, CtorName, Guard, RenderAs, TagId}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; @@ -2076,6 +2075,294 @@ impl<'a> Stmt<'a> { } } +fn from_can_let<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + def: Box, + cont: Box>, + variable: Variable, + hole: Option>, +) -> Stmt<'a> { + use roc_can::expr::Expr::*; + + macro_rules! lower_rest { + ($variable:expr, $expr:expr) => { + match hole { + None => from_can(env, $variable, $expr, procs, layout_cache), + Some(_) => unreachable!(), + } + }; + } + + if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value { + return match def.loc_expr.value { + Closure(closure_data) => { + register_capturing_closure(env, procs, layout_cache, *symbol, closure_data); + + lower_rest!(variable, cont.value) + } + Accessor(accessor_data) => { + let fresh_record_symbol = env.unique_symbol(); + register_noncapturing_closure( + env, + procs, + *symbol, + accessor_data.to_closure_data(fresh_record_symbol), + ); + + lower_rest!(variable, cont.value) + } + Var(original) | AbilityMember(original, _) => { + // a variable is aliased, e.g. + // + // foo = bar + // + // or + // + // foo = RBTRee.empty + + // TODO: right now we need help out rustc with the closure types; + // it isn't able to infer the right lifetime bounds. See if we + // can remove the annotations in the future. + let build_rest = + |env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>| { + from_can(env, def.expr_var, cont.value, procs, layout_cache) + }; + + return handle_variable_aliasing( + env, + procs, + layout_cache, + def.expr_var, + *symbol, + original, + build_rest, + ); + } + LetNonRec(nested_def, nested_cont) => { + use roc_can::expr::Expr::*; + // We must transform + // + // let answer = 1337 + // in + // let unused = + // let nested = 17 + // in + // nested + // in + // answer + // + // into + // + // let answer = 1337 + // in + // let nested = 17 + // in + // let unused = nested + // in + // answer + + let new_def = roc_can::def::Def { + loc_pattern: def.loc_pattern, + loc_expr: *nested_cont, + pattern_vars: def.pattern_vars, + annotation: def.annotation, + expr_var: def.expr_var, + }; + + let new_inner = LetNonRec(Box::new(new_def), cont); + + let new_outer = LetNonRec(nested_def, Box::new(Loc::at_zero(new_inner))); + + lower_rest!(variable, new_outer) + } + LetRec(nested_defs, nested_cont) => { + use roc_can::expr::Expr::*; + // We must transform + // + // let answer = 1337 + // in + // let unused = + // let nested = \{} -> nested {} + // in + // nested + // in + // answer + // + // into + // + // let answer = 1337 + // in + // let nested = \{} -> nested {} + // in + // let unused = nested + // in + // answer + + let new_def = roc_can::def::Def { + loc_pattern: def.loc_pattern, + loc_expr: *nested_cont, + pattern_vars: def.pattern_vars, + annotation: def.annotation, + expr_var: def.expr_var, + }; + + let new_inner = LetNonRec(Box::new(new_def), cont); + + let new_outer = LetRec(nested_defs, Box::new(Loc::at_zero(new_inner))); + + lower_rest!(variable, new_outer) + } + _ => { + let rest = lower_rest!(variable, cont.value); + + // Remove all the requested symbol specializations now, since this is the + // def site and hence we won't need them any higher up. + let mut needed_specializations = procs.symbol_specializations.remove(*symbol); + + match needed_specializations.len() { + 0 => { + // We don't need any specializations, that means this symbol is never + // referenced. + with_hole( + env, + def.loc_expr.value, + def.expr_var, + procs, + layout_cache, + *symbol, + env.arena.alloc(rest), + ) + } + + // We do need specializations + 1 => { + let (_specialization_mark, (var, specialized_symbol)) = + needed_specializations.next().unwrap(); + + // Unify the expr_var with the requested specialization once. + let _res = roc_unify::unify::unify(env.subs, var, def.expr_var, Mode::EQ); + + resolve_abilities_in_specialized_body( + env, + procs, + &def.loc_expr.value, + def.expr_var, + ); + + with_hole( + env, + def.loc_expr.value, + def.expr_var, + procs, + layout_cache, + specialized_symbol, + env.arena.alloc(rest), + ) + } + _n => { + let mut stmt = rest; + + // Need to eat the cost and create a specialized version of the body for + // each specialization. + for (_specialization_mark, (var, specialized_symbol)) in + needed_specializations + { + use crate::copy::deep_copy_type_vars_into_expr; + + let (new_def_expr_var, specialized_expr) = deep_copy_type_vars_into_expr( + env.arena, + env.subs, + def.expr_var, + &def.loc_expr.value, + ) + .expect( + "expr marked as having specializations, but it has no type variables!", + ); + + let _res = + roc_unify::unify::unify(env.subs, var, new_def_expr_var, Mode::EQ); + + resolve_abilities_in_specialized_body( + env, + procs, + &def.loc_expr.value, + def.expr_var, + ); + + stmt = with_hole( + env, + specialized_expr, + new_def_expr_var, + procs, + layout_cache, + specialized_symbol, + env.arena.alloc(stmt), + ); + } + + stmt + } + } + } + }; + } + + // this may be a destructure pattern + let (mono_pattern, assignments) = + match from_can_pattern(env, procs, layout_cache, &def.loc_pattern.value) { + Ok(v) => v, + Err(_) => todo!(), + }; + + // convert the continuation + let mut stmt = lower_rest!(variable, cont.value); + + // layer on any default record fields + for (symbol, variable, expr) in assignments { + let specialization_symbol = procs + .symbol_specializations + .remove_single(symbol) + // Can happen when the symbol was never used under this body, and hence has no + // requested specialization. + .unwrap_or(symbol); + + let hole = env.arena.alloc(stmt); + stmt = with_hole( + env, + expr, + variable, + procs, + layout_cache, + specialization_symbol, + hole, + ); + } + + if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value { + store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt) + } else { + let outer_symbol = env.unique_symbol(); + stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt); + + resolve_abilities_in_specialized_body(env, procs, &def.loc_expr.value, def.expr_var); + + // convert the def body, store in outer_symbol + with_hole( + env, + def.loc_expr.value, + def.expr_var, + procs, + layout_cache, + outer_symbol, + env.arena.alloc(stmt), + ) + } +} + /// turn record/tag patterns into a when expression, e.g. /// /// foo = \{ x } -> body @@ -5787,286 +6074,7 @@ pub fn from_can<'a>( from_can(env, variable, cont.value, procs, layout_cache) } - LetNonRec(def, cont) => { - if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value { - match def.loc_expr.value { - roc_can::expr::Expr::Closure(closure_data) => { - register_capturing_closure(env, procs, layout_cache, *symbol, closure_data); - - return from_can(env, variable, cont.value, procs, layout_cache); - } - roc_can::expr::Expr::Accessor(accessor_data) => { - let fresh_record_symbol = env.unique_symbol(); - register_noncapturing_closure( - env, - procs, - *symbol, - accessor_data.to_closure_data(fresh_record_symbol), - ); - - return from_can(env, variable, cont.value, procs, layout_cache); - } - roc_can::expr::Expr::Var(original) - | roc_can::expr::Expr::AbilityMember(original, _) => { - // a variable is aliased, e.g. - // - // foo = bar - // - // or - // - // foo = RBTRee.empty - - // TODO: right now we need help out rustc with the closure types; - // it isn't able to infer the right lifetime bounds. See if we - // can remove the annotations in the future. - let build_rest = - |env: &mut Env<'a, '_>, - procs: &mut Procs<'a>, - layout_cache: &mut LayoutCache<'a>| { - from_can(env, def.expr_var, cont.value, procs, layout_cache) - }; - - return handle_variable_aliasing( - env, - procs, - layout_cache, - def.expr_var, - *symbol, - original, - build_rest, - ); - } - roc_can::expr::Expr::LetNonRec(nested_def, nested_cont) => { - use roc_can::expr::Expr::*; - // We must transform - // - // let answer = 1337 - // in - // let unused = - // let nested = 17 - // in - // nested - // in - // answer - // - // into - // - // let answer = 1337 - // in - // let nested = 17 - // in - // let unused = nested - // in - // answer - - let new_def = roc_can::def::Def { - loc_pattern: def.loc_pattern, - loc_expr: *nested_cont, - pattern_vars: def.pattern_vars, - annotation: def.annotation, - expr_var: def.expr_var, - }; - - let new_inner = LetNonRec(Box::new(new_def), cont); - - let new_outer = LetNonRec(nested_def, Box::new(Loc::at_zero(new_inner))); - - return from_can(env, variable, new_outer, procs, layout_cache); - } - roc_can::expr::Expr::LetRec(nested_defs, nested_cont) => { - use roc_can::expr::Expr::*; - // We must transform - // - // let answer = 1337 - // in - // let unused = - // let nested = \{} -> nested {} - // in - // nested - // in - // answer - // - // into - // - // let answer = 1337 - // in - // let nested = \{} -> nested {} - // in - // let unused = nested - // in - // answer - - let new_def = roc_can::def::Def { - loc_pattern: def.loc_pattern, - loc_expr: *nested_cont, - pattern_vars: def.pattern_vars, - annotation: def.annotation, - expr_var: def.expr_var, - }; - - let new_inner = LetNonRec(Box::new(new_def), cont); - - let new_outer = LetRec(nested_defs, Box::new(Loc::at_zero(new_inner))); - - return from_can(env, variable, new_outer, procs, layout_cache); - } - _ => { - let rest = from_can(env, variable, cont.value, procs, layout_cache); - - // Remove all the requested symbol specializations now, since this is the - // def site and hence we won't need them any higher up. - let mut needed_specializations = - procs.symbol_specializations.remove(*symbol); - - if needed_specializations.len() == 0 { - // We don't need any specializations, that means this symbol is never - // referenced. - return with_hole( - env, - def.loc_expr.value, - def.expr_var, - procs, - layout_cache, - *symbol, - env.arena.alloc(rest), - ); - } - - // We do need specializations - - let mut stmt = rest; - - if needed_specializations.len() == 1 { - let (_specialization_mark, (var, specialized_symbol)) = - needed_specializations.next().unwrap(); - - // Unify the expr_var with the requested specialization once. - let _res = - roc_unify::unify::unify(env.subs, var, def.expr_var, Mode::EQ); - - resolve_abilities_in_specialized_body( - env, - procs, - &def.loc_expr.value, - def.expr_var, - ); - - return with_hole( - env, - def.loc_expr.value, - def.expr_var, - procs, - layout_cache, - specialized_symbol, - env.arena.alloc(stmt), - ); - } else { - // Need to eat the cost and create a specialized version of the body for each specialization. - for (_specialization_mark, (var, specialized_symbol)) in - needed_specializations - { - use crate::copy::deep_copy_type_vars_into_expr; - - let (new_def_expr_var, specialized_expr) = - deep_copy_type_vars_into_expr( - env.arena, - env.subs, - def.expr_var, - &def.loc_expr.value - ).expect("expr marked as having specializations, but it has no type variables!"); - - let _res = roc_unify::unify::unify( - env.subs, - var, - new_def_expr_var, - Mode::EQ, - ); - - resolve_abilities_in_specialized_body( - env, - procs, - &def.loc_expr.value, - def.expr_var, - ); - - stmt = with_hole( - env, - specialized_expr, - new_def_expr_var, - procs, - layout_cache, - specialized_symbol, - env.arena.alloc(stmt), - ); - } - - return stmt; - } - } - } - } - - // this may be a destructure pattern - let (mono_pattern, assignments) = - match from_can_pattern(env, procs, layout_cache, &def.loc_pattern.value) { - Ok(v) => v, - Err(_) => todo!(), - }; - - if let Pattern::Identifier(_symbol) = mono_pattern { - internal_error!("Identifier patterns should be handled in a higher code pass!") - } - - // convert the continuation - let mut stmt = from_can(env, variable, cont.value, procs, layout_cache); - - // layer on any default record fields - for (symbol, variable, expr) in assignments { - let specialization_symbol = procs - .symbol_specializations - .remove_single(symbol) - // Can happen when the symbol was never used under this body, and hence has no - // requested specialization. - .unwrap_or(symbol); - - let hole = env.arena.alloc(stmt); - stmt = with_hole( - env, - expr, - variable, - procs, - layout_cache, - specialization_symbol, - hole, - ); - } - - if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value { - store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt) - } else { - let outer_symbol = env.unique_symbol(); - stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt); - - resolve_abilities_in_specialized_body( - env, - procs, - &def.loc_expr.value, - def.expr_var, - ); - - // convert the def body, store in outer_symbol - with_hole( - env, - def.loc_expr.value, - def.expr_var, - procs, - layout_cache, - outer_symbol, - env.arena.alloc(stmt), - ) - } - } - + LetNonRec(def, cont) => from_can_let(env, procs, layout_cache, def, cont, variable, None), _ => { let symbol = env.unique_symbol(); let hole = env.arena.alloc(Stmt::Ret(symbol)); From bc5dc17ce978a563378d8a2e4d7980b7be276ed8 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 9 May 2022 16:29:20 -0400 Subject: [PATCH 2/5] Dead code --- compiler/mono/src/ir.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 9136861c05..3ebb53f783 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -3816,22 +3816,6 @@ pub fn with_hole<'a>( hole, ); } - // special-case the form `let x = E in x` - // not doing so will drop the `hole` - match &cont.value { - roc_can::expr::Expr::Var(original) if *original == symbol => { - return with_hole( - env, - def.loc_expr.value, - def.expr_var, - procs, - layout_cache, - assigned, - hole, - ); - } - _ => {} - } let build_rest = |env: &mut Env<'a, '_>, From a59739bf2048894a812d830bae90eaadaa525134 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 9 May 2022 16:36:13 -0400 Subject: [PATCH 3/5] More lifting --- compiler/mono/src/ir.rs | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 3ebb53f783..6f080371c7 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -2082,15 +2082,26 @@ fn from_can_let<'a>( def: Box, cont: Box>, variable: Variable, - hole: Option>, + opt_assigned_and_hole: Option<(Symbol, &'a Stmt<'a>)>, ) -> Stmt<'a> { use roc_can::expr::Expr::*; macro_rules! lower_rest { ($variable:expr, $expr:expr) => { - match hole { - None => from_can(env, $variable, $expr, procs, layout_cache), - Some(_) => unreachable!(), + lower_rest!(env, procs, layout_cache, $variable, $expr) + }; + ($env:expr, $procs:expr, $layout_cache:expr, $variable:expr, $expr:expr) => { + match opt_assigned_and_hole { + None => from_can($env, $variable, $expr, $procs, $layout_cache), + Some((assigned, hole)) => with_hole( + $env, + $expr, + $variable, + $procs, + $layout_cache, + assigned, + hole, + ), } }; } @@ -2129,7 +2140,7 @@ fn from_can_let<'a>( |env: &mut Env<'a, '_>, procs: &mut Procs<'a>, layout_cache: &mut LayoutCache<'a>| { - from_can(env, def.expr_var, cont.value, procs, layout_cache) + lower_rest!(env, procs, layout_cache, variable, cont.value) }; return handle_variable_aliasing( @@ -3803,17 +3814,15 @@ pub fn with_hole<'a>( } LetNonRec(def, cont) => { if let roc_can::pattern::Pattern::Identifier(symbol) = def.loc_pattern.value { - if let Closure(closure_data) = def.loc_expr.value { - register_noncapturing_closure(env, procs, symbol, closure_data); - - return with_hole( + if matches!(def.loc_expr.value, Closure(_)) { + return from_can_let( env, - cont.value, - variable, procs, layout_cache, - assigned, - hole, + def, + cont, + variable, + Some((assigned, hole)), ); } From 7c1a6037d4fe6538d97f322c0569a3849309488f Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 9 May 2022 16:39:06 -0400 Subject: [PATCH 4/5] Lift another branch --- compiler/mono/src/ir.rs | 69 ++++++----------------------------------- 1 file changed, 10 insertions(+), 59 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 6f080371c7..cf7f0abf20 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -3813,65 +3813,16 @@ pub fn with_hole<'a>( } } LetNonRec(def, cont) => { - if let roc_can::pattern::Pattern::Identifier(symbol) = def.loc_pattern.value { - if matches!(def.loc_expr.value, Closure(_)) { - return from_can_let( - env, - procs, - layout_cache, - def, - cont, - variable, - Some((assigned, hole)), - ); - } - - let build_rest = - |env: &mut Env<'a, '_>, - procs: &mut Procs<'a>, - layout_cache: &mut LayoutCache<'a>| { - with_hole( - env, - cont.value, - variable, - procs, - layout_cache, - assigned, - hole, - ) - }; - - // a variable is aliased - if let roc_can::expr::Expr::Var(original) = def.loc_expr.value { - // a variable is aliased, e.g. - // - // foo = bar - // - // or - // - // foo = RBTRee.empty - - handle_variable_aliasing( - env, - procs, - layout_cache, - def.expr_var, - symbol, - original, - build_rest, - ) - } else { - let rest = build_rest(env, procs, layout_cache); - with_hole( - env, - def.loc_expr.value, - def.expr_var, - procs, - layout_cache, - symbol, - env.arena.alloc(rest), - ) - } + if let roc_can::pattern::Pattern::Identifier(_) = def.loc_pattern.value { + from_can_let( + env, + procs, + layout_cache, + def, + cont, + variable, + Some((assigned, hole)), + ) } else { // this may be a destructure pattern let (mono_pattern, assignments) = From 1521711abda4996467bf20e0ecc45506316a323f Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 9 May 2022 16:45:39 -0400 Subject: [PATCH 5/5] Lift it all the way up --- compiler/mono/src/ir.rs | 65 ++++++----------------------------------- 1 file changed, 9 insertions(+), 56 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index cf7f0abf20..67e8880ce5 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -3812,62 +3812,15 @@ pub fn with_hole<'a>( } } } - LetNonRec(def, cont) => { - if let roc_can::pattern::Pattern::Identifier(_) = def.loc_pattern.value { - from_can_let( - env, - procs, - layout_cache, - def, - cont, - variable, - Some((assigned, hole)), - ) - } else { - // this may be a destructure pattern - let (mono_pattern, assignments) = - match from_can_pattern(env, procs, layout_cache, &def.loc_pattern.value) { - Ok(v) => v, - Err(_runtime_error) => { - // todo - panic!(); - } - }; - - let mut hole = hole; - - for (symbol, variable, expr) in assignments { - let stmt = with_hole(env, expr, variable, procs, layout_cache, symbol, hole); - - hole = env.arena.alloc(stmt); - } - - // convert the continuation - let mut stmt = with_hole( - env, - cont.value, - variable, - procs, - layout_cache, - assigned, - hole, - ); - - let outer_symbol = env.unique_symbol(); - stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt); - - // convert the def body, store in outer_symbol - with_hole( - env, - def.loc_expr.value, - def.expr_var, - procs, - layout_cache, - outer_symbol, - env.arena.alloc(stmt), - ) - } - } + LetNonRec(def, cont) => from_can_let( + env, + procs, + layout_cache, + def, + cont, + variable, + Some((assigned, hole)), + ), LetRec(defs, cont) => { // because Roc is strict, only functions can be recursive! for def in defs.into_iter() {