From 3e5627689f26bcae61d0ba2a0829b9fddef445ee Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 3 Jul 2020 21:36:50 -0400 Subject: [PATCH] Add basic inline function and a failing test --- compiler/can/src/expr.rs | 190 +++++++++++++++++++++++++++++++ compiler/can/tests/can_inline.rs | 65 +++++++++++ 2 files changed, 255 insertions(+) create mode 100644 compiler/can/tests/can_inline.rs diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 2ecf13c8d3..3e099cf3f3 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -1013,3 +1013,193 @@ fn canonicalize_lookup( (can_expr, output) } + +/// Currently uses the heuristic of "only inline if it's a builtin" +pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> Expr { + use Expr::*; + + match expr { + // Num stores the `a` variable in `Num a`. Not the same as the variable + // stored in Int and Float below, which is strictly for better error messages + other @ Num(_, _) + | other @ Int(_, _) + | other @ Float(_, _) + | other @ Str(_) + | other @ BlockStr(_) + | other @ RuntimeError(_) + | other @ EmptyRecord + | other @ Accessor { .. } + | other @ Update { .. } + | other @ Var(_) => other, + + List { + elem_var, + loc_elems, + } => { + let mut new_elems = Vec::with_capacity(loc_elems.len()); + + for loc_elem in loc_elems { + let value = inline_calls(var_store, scope, loc_elem.value); + + new_elems.push(Located { + value, + region: loc_elem.region, + }); + } + + List { + elem_var, + loc_elems: new_elems, + } + } + // Branching + When { + cond_var, + expr_var, + region, + loc_cond, + branches, + } => { + let loc_cond = Box::new(Located { + region: loc_cond.region, + value: inline_calls(var_store, scope, loc_cond.value), + }); + + let mut new_branches = Vec::with_capacity(branches.len()); + + for branch in branches { + let value = Located { + value: inline_calls(var_store, scope, branch.value.value), + region: branch.value.region, + }; + let guard = match branch.guard { + Some(loc_expr) => Some(Located { + region: loc_expr.region, + value: inline_calls(var_store, scope, loc_expr.value), + }), + None => None, + }; + let new_branch = WhenBranch { + patterns: branch.patterns, + value, + guard, + }; + + new_branches.push(new_branch); + } + + When { + cond_var, + expr_var, + region, + loc_cond, + branches: new_branches, + } + } + If { + cond_var, + branch_var, + branches, + final_else, + } => { + let mut new_branches = Vec::with_capacity(branches.len()); + + for (loc_cond, loc_expr) in branches { + let loc_cond = Located { + value: inline_calls(var_store, scope, loc_cond.value), + region: loc_cond.region, + }; + + let loc_expr = Located { + value: inline_calls(var_store, scope, loc_expr.value), + region: loc_expr.region, + }; + + new_branches.push((loc_cond, loc_expr)); + } + + let final_else = Box::new(Located { + region: final_else.region, + value: inline_calls(var_store, scope, final_else.value), + }); + + If { + cond_var, + branch_var, + branches: new_branches, + final_else, + } + } + + LetRec(_, _, _, _ /*Vec, Box>, Variable, Aliases */) => { + todo!("inline for LetRec"); + } + + LetNonRec(_, _, _, _ /*Box, Box>, Variable, Aliases*/) => { + todo!("inline for LetNonRec"); + } + + Closure(var, symbol, recursive, patterns, boxed_expr) => { + let (loc_expr, expr_var) = *boxed_expr; + let loc_expr = Located { + value: inline_calls(var_store, scope, loc_expr.value), + region: loc_expr.region, + }; + + Closure( + var, + symbol, + recursive, + patterns, + Box::new((loc_expr, expr_var)), + ) + } + + Record { record_var, fields } => { + todo!( + "Inlining for Record with record_var {:?} and fields {:?}", + record_var, + fields + ); + } + + Access { + record_var, + ext_var, + field_var, + loc_expr, + field, + } => { + todo!("Inlining for Access with record_var {:?}, ext_var {:?}, field_var {:?}, loc_expr {:?}, field {:?}", record_var, ext_var, field_var, loc_expr, field); + } + + Tag { + variant_var, + ext_var, + name, + arguments, + } => { + todo!( + "Inlining for Tag with variant_var {:?}, ext_var {:?}, name {:?}, arguments {:?}", + variant_var, + ext_var, + name, + arguments + ); + } + + Call(boxed_tuple, args, called_via) => { + let (fn_var, loc_expr, expr_var) = *boxed_tuple; + + match loc_expr.value { + Var(symbol) if symbol.is_builtin() => { + todo!("Inline this builtin: {:?}", symbol); + } + _ => { + // For now, we only inline calls to builtins. Leave this alone! + Call(Box::new((fn_var, loc_expr, expr_var)), args, called_via) + } + } + } + } +} diff --git a/compiler/can/tests/can_inline.rs b/compiler/can/tests/can_inline.rs new file mode 100644 index 0000000000..7275bc971e --- /dev/null +++ b/compiler/can/tests/can_inline.rs @@ -0,0 +1,65 @@ +#[macro_use] +extern crate pretty_assertions; +#[macro_use] +extern crate indoc; + +extern crate bumpalo; +extern crate roc_can; +extern crate roc_parse; +extern crate roc_region; + +mod helpers; + +#[cfg(test)] +mod can_inline { + use crate::helpers::{can_expr_with, test_home}; + use bumpalo::Bump; + use roc_can::expr::inline_calls; + use roc_can::expr::Expr::{self, *}; + use roc_can::scope::Scope; + use roc_module::operator::CalledVia; + use roc_module::symbol::Symbol; + use roc_region::all::{Located, Region}; + use roc_types::subs::VarStore; + + fn assert_inlines_to(input: &str, expected: Expr, var_store: &mut VarStore) { + let arena = Bump::new(); + let scope = &mut Scope::new(test_home()); + let actual_out = can_expr_with(&arena, test_home(), input); + let actual = inline_calls(var_store, scope, actual_out.loc_expr.value); + + assert_eq!(actual, expected); + } + + #[test] + fn inline_list_len() { + let var_store = &mut VarStore::default(); + + assert_inlines_to( + indoc!( + r#" + Int.isZero 5 + "# + ), + Expr::Call( + Box::new(( + var_store.fresh(), + Located { + region: Region::zero(), + value: Expr::Var(Symbol::FLOAT_EQ), + }, + var_store.fresh(), + )), + vec![( + var_store.fresh(), + Located { + region: Region::zero(), + value: Int(var_store.fresh(), 5), + }, + )], + CalledVia::Space, + ), + var_store, + ) + } +}