Use LayoutCache to avoid recomputing layouts

This commit is contained in:
Richard Feldman 2020-05-09 20:24:55 -04:00
parent efa486251e
commit 510ab36f51
6 changed files with 238 additions and 168 deletions

View file

@ -1,4 +1,4 @@
use crate::layout::{Builtin, Layout};
use crate::layout::{Builtin, Layout, LayoutCache};
use crate::pattern::{Ctor, Guard, RenderAs, TagId};
use bumpalo::collections::Vec;
use bumpalo::Bump;
@ -65,6 +65,7 @@ impl<'a> Procs<'a> {
loc_args: std::vec::Vec<(Variable, Located<roc_can::pattern::Pattern>)>,
loc_body: Located<roc_can::expr::Expr>,
ret_var: Variable,
layout_cache: &mut LayoutCache<'a>,
) {
let (arg_vars, arg_symbols, body) = patterns_to_when(env, loc_args, ret_var, loc_body);
@ -81,6 +82,7 @@ impl<'a> Procs<'a> {
&arg_symbols,
annotation,
body.value,
layout_cache,
)
.ok();
@ -248,7 +250,9 @@ impl<'a> Expr<'a> {
can_expr: roc_can::expr::Expr,
procs: &mut Procs<'a>,
) -> Self {
from_can(env, can_expr, procs, None)
let mut layout_cache = LayoutCache::default();
from_can(env, can_expr, procs, &mut layout_cache, None)
}
}
@ -448,6 +452,7 @@ fn from_can<'a>(
env: &mut Env<'a, '_>,
can_expr: roc_can::expr::Expr,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
name: Option<Symbol>,
) -> Expr<'a> {
use roc_can::expr::Expr::*;
@ -467,13 +472,23 @@ fn from_can<'a>(
let ret_var = partial_proc.annotation;
// This is a top-level declaration, which will code gen to a 0-arity thunk.
call_by_name(env, procs, fn_var, ret_var, symbol, std::vec::Vec::new())
call_by_name(
env,
procs,
fn_var,
ret_var,
symbol,
std::vec::Vec::new(),
layout_cache,
)
} else {
Expr::Load(symbol)
}
}
LetRec(defs, ret_expr, _, _) => from_can_defs(env, defs, *ret_expr, procs),
LetNonRec(def, ret_expr, _, _) => from_can_defs(env, vec![*def], *ret_expr, procs),
LetRec(defs, ret_expr, _, _) => from_can_defs(env, defs, *ret_expr, layout_cache, procs),
LetNonRec(def, ret_expr, _, _) => {
from_can_defs(env, vec![*def], *ret_expr, layout_cache, procs)
}
Closure(ann, original_name, _, loc_args, boxed_body) => {
let (loc_body, ret_var) = *boxed_body;
@ -484,7 +499,15 @@ fn from_can<'a>(
symbol
}
None => {
procs.insert_anonymous(env, original_name, ann, loc_args, loc_body, ret_var);
procs.insert_anonymous(
env,
original_name,
ann,
loc_args,
loc_body,
ret_var,
layout_cache,
);
original_name
}
@ -494,96 +517,19 @@ fn from_can<'a>(
}
Call(boxed, loc_args, _) => {
use IntOrFloat::*;
let (fn_var, loc_expr, ret_var) = *boxed;
let specialize_builtin_functions = {
|env: &mut Env<'a, '_>, symbol: Symbol| {
if !symbol.module_id().is_builtin() {
// return unchanged
symbol
} else {
match symbol {
Symbol::NUM_ADD => match num_to_int_or_float(env.subs, ret_var) {
FloatType => Symbol::FLOAT_ADD,
IntType => Symbol::INT_ADD,
},
Symbol::NUM_SUB => match num_to_int_or_float(env.subs, ret_var) {
FloatType => Symbol::FLOAT_SUB,
IntType => Symbol::INT_SUB,
},
Symbol::NUM_LTE => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_LTE,
IntType => Symbol::INT_LTE,
},
Symbol::NUM_LT => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_LT,
IntType => Symbol::INT_LT,
},
Symbol::NUM_GTE => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_GTE,
IntType => Symbol::INT_GTE,
},
Symbol::NUM_GT => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_GT,
IntType => Symbol::INT_GT,
},
// TODO make this work for more than just int/float
Symbol::BOOL_EQ => {
match Layout::from_var(
env.arena,
loc_args[0].0,
env.subs,
env.pointer_size,
) {
Ok(Layout::Builtin(builtin)) => match builtin {
Builtin::Int64 => Symbol::INT_EQ_I64,
Builtin::Float64 => Symbol::FLOAT_EQ,
Builtin::Bool => Symbol::INT_EQ_I1,
Builtin::Byte => Symbol::INT_EQ_I8,
_ => panic!("Equality not implemented for {:?}", builtin),
},
Ok(complex) => panic!(
"TODO support equality on complex layouts like {:?}",
complex
),
Err(()) => panic!("Invalid layout"),
}
}
Symbol::BOOL_NEQ => {
match Layout::from_var(
env.arena,
loc_args[0].0,
env.subs,
env.pointer_size,
) {
Ok(Layout::Builtin(builtin)) => match builtin {
Builtin::Int64 => Symbol::INT_NEQ_I64,
Builtin::Bool => Symbol::INT_NEQ_I1,
Builtin::Byte => Symbol::INT_NEQ_I8,
_ => {
panic!("Not-Equality not implemented for {:?}", builtin)
}
},
Ok(complex) => panic!(
"TODO support equality on complex layouts like {:?}",
complex
),
Err(()) => panic!("Invalid layout"),
}
}
_ => symbol,
}
}
}
};
match from_can(env, loc_expr.value, procs, None) {
match from_can(env, loc_expr.value, procs, layout_cache, None) {
Expr::Load(proc_name) => {
// Some functions can potentially mutate in-place.
// If we have one of those, switch to the in-place version if appropriate.
match specialize_builtin_functions(env, proc_name) {
match specialize_builtin_functions(
env,
proc_name,
loc_args.as_slice(),
ret_var,
layout_cache,
) {
Symbol::LIST_SET => {
let subs = &env.subs;
// The first arg is the one with the List in it.
@ -610,9 +556,25 @@ fn from_can<'a>(
Symbol::LIST_SET
};
call_by_name(env, procs, fn_var, ret_var, new_name, loc_args)
call_by_name(
env,
procs,
fn_var,
ret_var,
new_name,
loc_args,
layout_cache,
)
}
_ => call_by_name(env, procs, fn_var, ret_var, proc_name, loc_args),
_ => call_by_name(
env,
procs,
fn_var,
ret_var,
proc_name,
loc_args,
layout_cache,
),
}
}
specialized_proc_symbol => call_by_name(
@ -622,6 +584,7 @@ fn from_can<'a>(
ret_var,
specialized_proc_symbol,
loc_args,
layout_cache,
),
}
}
@ -640,10 +603,11 @@ fn from_can<'a>(
let mut args = Vec::with_capacity_in(loc_args.len(), env.arena);
for (_, loc_arg) in loc_args {
args.push(from_can(env, loc_arg.value, procs, None));
args.push(from_can(env, loc_arg.value, procs, layout_cache, None));
}
let layout = Layout::from_var(env.arena, fn_var, env.subs, env.pointer_size)
let layout = layout_cache
.from_var(env.arena, fn_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| {
panic!("TODO turn fn_var into a RuntimeError {:?}", err)
});
@ -658,7 +622,16 @@ fn from_can<'a>(
region,
loc_cond,
branches,
} => from_can_when(env, cond_var, expr_var, region, *loc_cond, branches, procs),
} => from_can_when(
env,
cond_var,
expr_var,
region,
*loc_cond,
branches,
layout_cache,
procs,
),
If {
cond_var,
@ -666,16 +639,18 @@ fn from_can<'a>(
branches,
final_else,
} => {
let mut expr = from_can(env, final_else.value, procs, None);
let mut expr = from_can(env, final_else.value, procs, layout_cache, None);
let ret_layout = Layout::from_var(env.arena, branch_var, env.subs, env.pointer_size)
let ret_layout = layout_cache
.from_var(env.arena, branch_var, env.subs, env.pointer_size)
.expect("invalid ret_layout");
let cond_layout = Layout::from_var(env.arena, cond_var, env.subs, env.pointer_size)
let cond_layout = layout_cache
.from_var(env.arena, cond_var, env.subs, env.pointer_size)
.expect("invalid cond_layout");
for (loc_cond, loc_then) in branches.into_iter().rev() {
let cond = from_can(env, loc_cond.value, procs, None);
let then = from_can(env, loc_then.value, procs, None);
let cond = from_can(env, loc_cond.value, procs, layout_cache, None);
let then = from_can(env, loc_then.value, procs, layout_cache, None);
let branch_symbol = env.unique_symbol();
@ -716,7 +691,7 @@ fn from_can<'a>(
for (label, layout) in btree {
let field = fields.remove(&label).unwrap();
let expr = from_can(env, field.loc_expr.value, procs, None);
let expr = from_can(env, field.loc_expr.value, procs, layout_cache, None);
field_tuples.push((expr, layout));
}
@ -757,7 +732,7 @@ fn from_can<'a>(
Unwrapped(field_layouts) => {
let field_exprs = args
.into_iter()
.map(|(_, arg)| from_can(env, arg.value, procs, None));
.map(|(_, arg)| from_can(env, arg.value, procs, layout_cache, None));
let mut field_tuples = Vec::with_capacity_in(field_layouts.len(), arena);
@ -779,7 +754,7 @@ fn from_can<'a>(
let it = std::iter::once(Expr::Int(tag_id as i64)).chain(
args.into_iter()
.map(|(_, arg)| from_can(env, arg.value, procs, None)),
.map(|(_, arg)| from_can(env, arg.value, procs, layout_cache, None)),
);
for (arg_layout, arg_expr) in argument_layouts.iter().zip(it) {
@ -832,7 +807,7 @@ fn from_can<'a>(
}
}
let record = arena.alloc(from_can(env, loc_expr.value, procs, None));
let record = arena.alloc(from_can(env, loc_expr.value, procs, layout_cache, None));
Expr::AccessAtIndex {
index: index.expect("field not in its own type") as u64,
@ -853,8 +828,8 @@ fn from_can<'a>(
// We have to special-case the empty list, because trying to
// compute a layout for an unbound var won't work.
Content::FlexVar(_) => Layout::Builtin(Builtin::EmptyList),
content => match Layout::from_content(arena, content, env.subs, env.pointer_size) {
Ok(layout) => layout,
_ => match layout_cache.from_var(arena, elem_var, env.subs, env.pointer_size) {
Ok(layout) => layout.clone(),
Err(()) => {
panic!("TODO gracefully handle List with invalid element layout");
}
@ -864,7 +839,7 @@ fn from_can<'a>(
let mut elems = Vec::with_capacity_in(loc_elems.len(), arena);
for loc_elem in loc_elems {
elems.push(from_can(env, loc_elem.value, procs, None));
elems.push(from_can(env, loc_elem.value, procs, layout_cache, None));
}
Expr::Array {
@ -1022,6 +997,7 @@ fn from_can_defs<'a>(
env: &mut Env<'a, '_>,
defs: std::vec::Vec<roc_can::def::Def>,
ret_expr: Located<roc_can::expr::Expr>,
layout_cache: &mut LayoutCache<'a>,
procs: &mut Procs<'a>,
) -> Expr<'a> {
use roc_can::expr::Expr::*;
@ -1049,7 +1025,7 @@ fn from_can_defs<'a>(
if let Closure(_, _, _, _, _) = &loc_expr.value {
// Extract Procs, but discard the resulting Expr::Load.
// That Load looks up the pointer, which we won't use here!
from_can(env, loc_expr.value, procs, Some(*symbol));
from_can(env, loc_expr.value, procs, layout_cache, Some(*symbol));
continue;
}
@ -1058,7 +1034,8 @@ fn from_can_defs<'a>(
// If it wasn't specifically an Identifier & Closure, proceed as normal.
let mono_pattern = from_can_pattern(env, &loc_pattern.value);
let layout = Layout::from_var(env.arena, def.expr_var, env.subs, env.pointer_size)
let layout = layout_cache
.from_var(env.arena, def.expr_var, env.subs, env.pointer_size)
.expect("invalid layout");
match &mono_pattern {
@ -1066,7 +1043,7 @@ fn from_can_defs<'a>(
stored.push((
*symbol,
layout.clone(),
from_can(env, loc_expr.value, procs, None),
from_can(env, loc_expr.value, procs, layout_cache, None),
));
}
_ => {
@ -1093,7 +1070,7 @@ fn from_can_defs<'a>(
stored.push((
symbol,
layout.clone(),
from_can(env, loc_expr.value, procs, None),
from_can(env, loc_expr.value, procs, layout_cache, None),
));
match store_pattern(env, &mono_pattern, symbol, layout, &mut stored) {
@ -1108,7 +1085,7 @@ fn from_can_defs<'a>(
}
// At this point, it's safe to assume we aren't assigning a Closure to a def.
// Extract Procs from the def body and the ret expression, and return the result!
let ret = from_can(env, ret_expr.value, procs, None);
let ret = from_can(env, ret_expr.value, procs, layout_cache, None);
if stored.is_empty() {
ret
@ -1124,6 +1101,7 @@ fn from_can_when<'a>(
region: Region,
loc_cond: Located<roc_can::expr::Expr>,
mut branches: std::vec::Vec<roc_can::expr::WhenBranch>,
layout_cache: &mut LayoutCache<'a>,
procs: &mut Procs<'a>,
) -> Expr<'a> {
if branches.is_empty() {
@ -1168,32 +1146,34 @@ fn from_can_when<'a>(
}
}
let cond_layout = Layout::from_var(env.arena, cond_var, env.subs, env.pointer_size)
let cond_layout = layout_cache
.from_var(env.arena, cond_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err));
let cond_symbol = env.unique_symbol();
let cond = from_can(env, loc_cond.value, procs, None);
let cond = from_can(env, loc_cond.value, procs, layout_cache, None);
stored.push((cond_symbol, cond_layout.clone(), cond));
// NOTE this will still store shadowed names.
// that's fine: the branch throws a runtime error anyway
let ret = match store_pattern(env, &mono_pattern, cond_symbol, cond_layout, &mut stored) {
Ok(_) => from_can(env, first.value.value, procs, None),
Ok(_) => from_can(env, first.value.value, procs, layout_cache, None),
Err(message) => Expr::RuntimeError(env.arena.alloc(message)),
};
Expr::Store(stored.into_bump_slice(), arena.alloc(ret))
} else {
let cond_layout = Layout::from_var(env.arena, cond_var, env.subs, env.pointer_size)
let cond_layout = layout_cache
.from_var(env.arena, cond_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err));
let cond = from_can(env, loc_cond.value, procs, None);
let cond = from_can(env, loc_cond.value, procs, layout_cache, None);
let cond_symbol = env.unique_symbol();
let mut loc_branches = std::vec::Vec::new();
let mut opt_branches = std::vec::Vec::new();
for when_branch in branches {
let mono_expr = from_can(env, when_branch.value.value, procs, None);
let mono_expr = from_can(env, when_branch.value.value, procs, layout_cache, None);
let exhaustive_guard = if when_branch.guard.is_some() {
Guard::HasGuard
@ -1226,7 +1206,7 @@ fn from_can_when<'a>(
//
// otherwise, we modify the branch's expression to include the stores
if let Some(loc_guard) = when_branch.guard.clone() {
let expr = from_can(env, loc_guard.value, procs, None);
let expr = from_can(env, loc_guard.value, procs, layout_cache, None);
(
crate::decision_tree::Guard::Guard {
stores: stores.into_bump_slice(),
@ -1306,7 +1286,8 @@ fn from_can_when<'a>(
}
}
let ret_layout = Layout::from_var(env.arena, expr_var, env.subs, env.pointer_size)
let ret_layout = layout_cache
.from_var(env.arena, expr_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err));
let branching = crate::decision_tree::optimize_when(
@ -1330,6 +1311,7 @@ fn call_by_name<'a>(
ret_var: Variable,
proc_name: Symbol,
loc_args: std::vec::Vec<(Variable, Located<roc_can::expr::Expr>)>,
layout_cache: &mut LayoutCache<'a>,
) -> Expr<'a> {
// create specialized procedure to call
@ -1395,6 +1377,7 @@ fn call_by_name<'a>(
&loc_patterns,
annotation,
body,
layout_cache,
)
.ok();
@ -1405,10 +1388,14 @@ fn call_by_name<'a>(
let mut args = Vec::with_capacity_in(loc_args.len(), env.arena);
for (var, loc_arg) in loc_args {
let layout = Layout::from_var(&env.arena, var, &env.subs, env.pointer_size)
let layout = layout_cache
.from_var(&env.arena, var, &env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO gracefully handle bad layout: {:?}", err));
args.push((from_can(env, loc_arg.value, procs, None), layout));
args.push((
from_can(env, loc_arg.value, procs, layout_cache, None),
layout,
));
}
Expr::CallByName(specialized_proc_name, args.into_bump_slice())
@ -1425,23 +1412,26 @@ fn specialize_proc_body<'a>(
pattern_symbols: &[Symbol],
annotation: Variable,
body: roc_can::expr::Expr,
layout_cache: &mut LayoutCache<'a>,
) -> Result<Proc<'a>, ()> {
// unify the called function with the specialized signature, then specialize the function body
let snapshot = env.subs.snapshot();
let unified = roc_unify::unify::unify(env.subs, annotation, fn_var);
debug_assert!(matches!(unified, roc_unify::unify::Unified::Success(_)));
let specialized_body = from_can(env, body, procs, None);
let specialized_body = from_can(env, body, procs, layout_cache, None);
// reset subs, so we don't get type errors when specializing for a different signature
env.subs.rollback_to(snapshot);
let mut proc_args = Vec::with_capacity_in(loc_args.len(), &env.arena);
for (arg_var, arg_name) in loc_args.iter().zip(pattern_symbols.iter()) {
let layout = Layout::from_var(&env.arena, *arg_var, env.subs, env.pointer_size)?;
let layout = layout_cache.from_var(&env.arena, *arg_var, env.subs, env.pointer_size)?;
proc_args.push((layout, *arg_name));
}
let ret_layout = Layout::from_var(&env.arena, ret_var, env.subs, env.pointer_size)
let ret_layout = layout_cache
.from_var(&env.arena, ret_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err));
let proc = Proc {
@ -1768,3 +1758,78 @@ pub fn specialize_equality<'a>(
arena.alloc([(lhs, layout.clone()), (rhs, layout.clone())]),
)
}
fn specialize_builtin_functions<'a>(
env: &mut Env<'a, '_>,
symbol: Symbol,
loc_args: &[(Variable, Located<roc_can::expr::Expr>)],
ret_var: Variable,
layout_cache: &mut LayoutCache<'a>,
) -> Symbol {
use IntOrFloat::*;
if !symbol.is_builtin() {
// return unchanged
symbol
} else {
match symbol {
Symbol::NUM_ADD => match num_to_int_or_float(env.subs, ret_var) {
FloatType => Symbol::FLOAT_ADD,
IntType => Symbol::INT_ADD,
},
Symbol::NUM_SUB => match num_to_int_or_float(env.subs, ret_var) {
FloatType => Symbol::FLOAT_SUB,
IntType => Symbol::INT_SUB,
},
Symbol::NUM_LTE => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_LTE,
IntType => Symbol::INT_LTE,
},
Symbol::NUM_LT => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_LT,
IntType => Symbol::INT_LT,
},
Symbol::NUM_GTE => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_GTE,
IntType => Symbol::INT_GTE,
},
Symbol::NUM_GT => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_GT,
IntType => Symbol::INT_GT,
},
// TODO make this work for more than just int/float
Symbol::BOOL_EQ => {
match layout_cache.from_var(env.arena, loc_args[0].0, env.subs, env.pointer_size) {
Ok(Layout::Builtin(builtin)) => match builtin {
Builtin::Int64 => Symbol::INT_EQ_I64,
Builtin::Float64 => Symbol::FLOAT_EQ,
Builtin::Bool => Symbol::INT_EQ_I1,
Builtin::Byte => Symbol::INT_EQ_I8,
_ => panic!("Equality not implemented for {:?}", builtin),
},
Ok(complex) => panic!(
"TODO support equality on complex layouts like {:?}",
complex
),
Err(()) => panic!("Invalid layout"),
}
}
Symbol::BOOL_NEQ => {
match layout_cache.from_var(env.arena, loc_args[0].0, env.subs, env.pointer_size) {
Ok(Layout::Builtin(builtin)) => match builtin {
Builtin::Int64 => Symbol::INT_NEQ_I64,
Builtin::Bool => Symbol::INT_NEQ_I1,
Builtin::Byte => Symbol::INT_NEQ_I8,
_ => panic!("Not-Equality not implemented for {:?}", builtin),
},
Ok(complex) => panic!(
"TODO support equality on complex layouts like {:?}",
complex
),
Err(()) => panic!("Invalid layout"),
}
}
_ => symbol,
}
}
}