change pattern destructuring stores

they are now tracked in the Cond/Switch variants, rather than merged into the branch constructors. Required for 'beans'
This commit is contained in:
Folkert 2020-03-23 23:35:45 +01:00
parent 2bb69f333f
commit 958f3f9ad4
6 changed files with 157 additions and 101 deletions

View file

@ -77,13 +77,17 @@ pub fn build_expr<'a, B: Backend>(
Bool(val) => builder.ins().bconst(types::B1, *val), Bool(val) => builder.ins().bconst(types::B1, *val),
Byte(val) => builder.ins().iconst(types::I8, *val as i64), Byte(val) => builder.ins().iconst(types::I8, *val as i64),
Cond { Cond {
cond, branch_symbol,
pass, pass: (pass_stores, pass_expr),
fail, fail: (fail_stores, fail_expr),
cond_layout, cond_layout,
ret_layout, ret_layout,
..
} => { } => {
let cond_value = load_symbol(env, scope, builder, *cond); let cond_value = load_symbol(env, scope, builder, *branch_symbol);
let pass = env.arena.alloc(Expr::Store(pass_stores, pass_expr));
let fail = env.arena.alloc(Expr::Store(fail_stores, fail_expr));
let branch = Branch2 { let branch = Branch2 {
cond: cond_value, cond: cond_value,
@ -98,15 +102,24 @@ pub fn build_expr<'a, B: Backend>(
Switch { Switch {
cond, cond,
branches, branches,
default_branch, default_branch: (default_stores, default_expr),
ret_layout, ret_layout,
cond_layout, cond_layout,
} => { } => {
let ret_type = type_from_layout(env.cfg, &ret_layout); let ret_type = type_from_layout(env.cfg, &ret_layout);
let default_branch = env.arena.alloc(Expr::Store(default_stores, default_expr));
let mut combined = Vec::with_capacity_in(branches.len(), env.arena);
for (int, stores, expr) in branches.iter() {
combined.push((*int, Expr::Store(stores, expr)));
}
let switch_args = SwitchArgs { let switch_args = SwitchArgs {
cond_layout, cond_layout,
cond_expr: cond, cond_expr: cond,
branches, branches: combined.into_bump_slice(),
default_branch, default_branch,
ret_type, ret_type,
}; };

View file

@ -57,14 +57,17 @@ pub fn build_expr<'a, 'ctx, 'env>(
Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(), Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(),
Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(), Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(),
Cond { Cond {
cond, branch_symbol,
pass, pass: (pass_stores, pass_expr),
fail, fail: (fail_stores, fail_expr),
ret_layout, ret_layout,
.. ..
} => { } => {
let pass = env.arena.alloc(Expr::Store(pass_stores, pass_expr));
let fail = env.arena.alloc(Expr::Store(fail_stores, fail_expr));
let conditional = Branch2 { let conditional = Branch2 {
cond, cond: branch_symbol,
pass, pass,
fail, fail,
ret_layout: ret_layout.clone(), ret_layout: ret_layout.clone(),
@ -75,16 +78,25 @@ pub fn build_expr<'a, 'ctx, 'env>(
Switch { Switch {
cond, cond,
branches, branches,
default_branch, default_branch: (default_stores, default_expr),
ret_layout, ret_layout,
cond_layout, cond_layout,
} => { } => {
let ret_type = let ret_type =
basic_type_from_layout(env.arena, env.context, &ret_layout, env.ptr_bytes); basic_type_from_layout(env.arena, env.context, &ret_layout, env.ptr_bytes);
let default_branch = env.arena.alloc(Expr::Store(default_stores, default_expr));
let mut combined = Vec::with_capacity_in(branches.len(), env.arena);
for (int, stores, expr) in branches.iter() {
combined.push((*int, Expr::Store(stores, expr)));
}
let switch_args = SwitchArgs { let switch_args = SwitchArgs {
cond_layout: cond_layout.clone(), cond_layout: cond_layout.clone(),
cond_expr: cond, cond_expr: cond,
branches, branches: combined.into_bump_slice(),
default_branch, default_branch,
ret_type, ret_type,
}; };

View file

@ -856,26 +856,31 @@ enum Decider<'a, T> {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
enum Choice<'a> { enum Choice<'a> {
Inline(Expr<'a>), Inline(Stores<'a>, Expr<'a>),
Jump(Label), Jump(Label),
} }
type Stores<'a> = &'a [(Symbol, Layout<'a>, Expr<'a>)];
pub fn optimize_when<'a>( pub fn optimize_when<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
cond_symbol: Symbol, cond_symbol: Symbol,
cond_layout: Layout<'a>, cond_layout: Layout<'a>,
ret_layout: Layout<'a>, ret_layout: Layout<'a>,
opt_branches: Vec<(Pattern<'a>, Guard<'a>, Expr<'a>)>, opt_branches: Vec<(Pattern<'a>, Guard<'a>, Stores<'a>, Expr<'a>)>,
) -> Expr<'a> { ) -> Expr<'a> {
let (patterns, _indexed_branches) = opt_branches let (patterns, _indexed_branches) = opt_branches
.into_iter() .into_iter()
.enumerate() .enumerate()
.map(|(index, (pattern, guard, branch))| { .map(|(index, (pattern, guard, stores, branch))| {
((guard, pattern, index as u64), (index as u64, branch)) (
(guard, pattern, index as u64),
(index as u64, stores, branch),
)
}) })
.unzip(); .unzip();
let indexed_branches: Vec<(u64, Expr<'a>)> = _indexed_branches; let indexed_branches: Vec<(u64, Stores<'a>, Expr<'a>)> = _indexed_branches;
let decision_tree = compile(patterns); let decision_tree = compile(patterns);
let decider = tree_to_decider(decision_tree); let decider = tree_to_decider(decision_tree);
@ -884,8 +889,9 @@ pub fn optimize_when<'a>(
let mut choices = MutMap::default(); let mut choices = MutMap::default();
let mut jumps = Vec::new(); let mut jumps = Vec::new();
for (index, branch) in indexed_branches.into_iter() { for (index, stores, branch) in indexed_branches.into_iter() {
let ((branch_index, choice), opt_jump) = create_choices(&target_counts, index, branch); let ((branch_index, choice), opt_jump) =
create_choices(&target_counts, index, stores, branch);
if let Some(jump) = opt_jump { if let Some(jump) = opt_jump {
jumps.push(jump); jumps.push(jump);
@ -896,7 +902,7 @@ pub fn optimize_when<'a>(
let choice_decider = insert_choices(&choices, decider); let choice_decider = insert_choices(&choices, decider);
let result = decide_to_branching( let (stores, expr) = decide_to_branching(
env, env,
cond_symbol, cond_symbol,
cond_layout, cond_layout,
@ -908,7 +914,7 @@ pub fn optimize_when<'a>(
// increase the jump counter by the number of jumps in this branching structure // increase the jump counter by the number of jumps in this branching structure
*env.jump_counter += jumps.len() as u64; *env.jump_counter += jumps.len() as u64;
result Expr::Store(stores, env.arena.alloc(expr))
} }
fn path_to_expr<'a>( fn path_to_expr<'a>(
@ -1064,16 +1070,16 @@ fn decide_to_branching<'a>(
cond_layout: Layout<'a>, cond_layout: Layout<'a>,
ret_layout: Layout<'a>, ret_layout: Layout<'a>,
decider: Decider<'a, Choice<'a>>, decider: Decider<'a, Choice<'a>>,
jumps: &Vec<(u64, Expr<'a>)>, jumps: &Vec<(u64, Stores<'a>, Expr<'a>)>,
) -> Expr<'a> { ) -> (Stores<'a>, Expr<'a>) {
use Choice::*; use Choice::*;
use Decider::*; use Decider::*;
let jump_count = *env.jump_counter; let jump_count = *env.jump_counter;
match decider { match decider {
Leaf(Jump(label)) => Expr::Jump(label + jump_count), Leaf(Jump(label)) => (&[], Expr::Jump(label + jump_count)),
Leaf(Inline(expr)) => expr, Leaf(Inline(stores, expr)) => (stores, expr),
Chain { Chain {
test_chain, test_chain,
success, success,
@ -1087,52 +1093,49 @@ fn decide_to_branching<'a>(
test_to_equality(env, cond_symbol, &cond_layout, &path, test, &mut tests); test_to_equality(env, cond_symbol, &cond_layout, &path, test, &mut tests);
} }
let pass = env.arena.alloc(decide_to_branching( let (pass_stores, pass_expr) = decide_to_branching(
env, env,
cond_symbol, cond_symbol,
cond_layout.clone(), cond_layout.clone(),
ret_layout.clone(), ret_layout.clone(),
*success, *success,
jumps, jumps,
)); );
let fail = env.arena.alloc(decide_to_branching( let (fail_stores, fail_expr) = decide_to_branching(
env, env,
cond_symbol, cond_symbol,
cond_layout.clone(), cond_layout.clone(),
ret_layout.clone(), ret_layout.clone(),
*failure, *failure,
jumps, jumps,
)); );
let fail = (fail_stores, &*env.arena.alloc(fail_expr));
let pass = (pass_stores, &*env.arena.alloc(pass_expr));
let condition = boolean_all(env.arena, tests); let condition = boolean_all(env.arena, tests);
let branch_symbol = env.fresh_symbol();
let stores = [(branch_symbol, Layout::Builtin(Builtin::Bool), condition)];
let cond_layout = Layout::Builtin(Builtin::Bool); let cond_layout = Layout::Builtin(Builtin::Bool);
if let Expr::Load(symbol) = condition { (
Expr::Cond {
cond: symbol,
cond_layout,
pass,
fail,
ret_layout,
}
} else {
let cond_symbol = env.fresh_symbol();
let stores = vec![(cond_symbol, cond_layout.clone(), condition)];
Expr::Store(
env.arena.alloc(stores), env.arena.alloc(stores),
Expr::Store(
&[],
env.arena.alloc(Expr::Cond { env.arena.alloc(Expr::Cond {
cond: cond_symbol, cond_symbol,
branch_symbol,
cond_layout, cond_layout,
pass, pass,
fail, fail,
ret_layout, ret_layout,
}), }),
),
) )
} }
}
FanOut { FanOut {
path, path,
tests, tests,
@ -1142,19 +1145,20 @@ fn decide_to_branching<'a>(
// switch on the tag discriminant (currently an i64 value) // switch on the tag discriminant (currently an i64 value)
let (cond, cond_layout) = path_to_expr_help(env, cond_symbol, &path, cond_layout); let (cond, cond_layout) = path_to_expr_help(env, cond_symbol, &path, cond_layout);
let default_branch = env.arena.alloc(decide_to_branching( let (default_stores, default_expr) = decide_to_branching(
env, env,
cond_symbol, cond_symbol,
cond_layout.clone(), cond_layout.clone(),
ret_layout.clone(), ret_layout.clone(),
*fallback, *fallback,
jumps, jumps,
)); );
let default_branch = (default_stores, &*env.arena.alloc(default_expr));
let mut branches = bumpalo::collections::Vec::with_capacity_in(tests.len(), env.arena); let mut branches = bumpalo::collections::Vec::with_capacity_in(tests.len(), env.arena);
for (test, decider) in tests { for (test, decider) in tests {
let branch = decide_to_branching( let (stores, branch) = decide_to_branching(
env, env,
cond_symbol, cond_symbol,
cond_layout.clone(), cond_layout.clone(),
@ -1172,17 +1176,20 @@ fn decide_to_branching<'a>(
other => todo!("other {:?}", other), other => todo!("other {:?}", other),
}; };
branches.push((tag, branch)); branches.push((tag, stores, branch));
} }
// make a jump table based on the tests // make a jump table based on the tests
(
&[],
Expr::Switch { Expr::Switch {
cond: env.arena.alloc(cond), cond: env.arena.alloc(cond),
cond_layout, cond_layout,
branches: branches.into_bump_slice(), branches: branches.into_bump_slice(),
default_branch, default_branch,
ret_layout, ret_layout,
} },
)
} }
} }
} }
@ -1372,18 +1379,23 @@ fn count_targets_help(decision_tree: &Decider<u64>, targets: &mut MutMap<u64, u6
} }
} }
#[allow(clippy::type_complexity)]
fn create_choices<'a>( fn create_choices<'a>(
target_counts: &MutMap<u64, u64>, target_counts: &MutMap<u64, u64>,
target: u64, target: u64,
stores: Stores<'a>,
branch: Expr<'a>, branch: Expr<'a>,
) -> ((u64, Choice<'a>), Option<(u64, Expr<'a>)>) { ) -> ((u64, Choice<'a>), Option<(u64, Stores<'a>, Expr<'a>)>) {
match target_counts.get(&target) { match target_counts.get(&target) {
None => unreachable!( None => unreachable!(
"this should never happen: {:?} not in {:?}", "this should never happen: {:?} not in {:?}",
target, target_counts target, target_counts
), ),
Some(1) => ((target, Choice::Inline(branch)), None), Some(1) => ((target, Choice::Inline(stores, branch)), None),
Some(_) => ((target, Choice::Jump(target)), Some((target, branch))), Some(_) => (
(target, Choice::Jump(target)),
Some((target, stores, branch)),
),
} }
} }

View file

@ -122,6 +122,8 @@ impl<'a, 'i> Env<'a, 'i> {
} }
} }
pub type Stores<'a> = &'a [(Symbol, Layout<'a>, Expr<'a>)];
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Expr<'a> { pub enum Expr<'a> {
// Literals // Literals
@ -152,11 +154,18 @@ pub enum Expr<'a> {
// The left-hand side of the conditional comparison and the right-hand side. // The left-hand side of the conditional comparison and the right-hand side.
// These are stored separately because there are different machine instructions // These are stored separately because there are different machine instructions
// for e.g. "compare float and jump" vs. "compare integer and jump" // for e.g. "compare float and jump" vs. "compare integer and jump"
cond: Symbol,
// symbol storing the original expression that we branch on, e.g. `Ok 42`
// required for RC logic
cond_symbol: Symbol,
// symbol storing the value that we branch on, e.g. `1` representing the `Ok` tag
branch_symbol: Symbol,
cond_layout: Layout<'a>, cond_layout: Layout<'a>,
// What to do if the condition either passes or fails // What to do if the condition either passes or fails
pass: &'a Expr<'a>, pass: (Stores<'a>, &'a Expr<'a>),
fail: &'a Expr<'a>, fail: (Stores<'a>, &'a Expr<'a>),
ret_layout: Layout<'a>, ret_layout: Layout<'a>,
}, },
/// Conditional branches for integers. These are more efficient. /// Conditional branches for integers. These are more efficient.
@ -166,9 +175,9 @@ pub enum Expr<'a> {
cond_layout: Layout<'a>, cond_layout: Layout<'a>,
/// The u64 in the tuple will be compared directly to the condition Expr. /// The u64 in the tuple will be compared directly to the condition Expr.
/// If they are equal, this branch will be taken. /// If they are equal, this branch will be taken.
branches: &'a [(u64, Expr<'a>)], branches: &'a [(u64, Stores<'a>, Expr<'a>)],
/// If no other branches pass, this default branch will be taken. /// If no other branches pass, this default branch will be taken.
default_branch: &'a Expr<'a>, default_branch: (Stores<'a>, &'a Expr<'a>),
/// Each branch must return a value of this type. /// Each branch must return a value of this type.
ret_layout: Layout<'a>, ret_layout: Layout<'a>,
}, },
@ -647,19 +656,20 @@ fn from_can<'a>(
let cond = from_can(env, loc_cond.value, procs, None); let cond = from_can(env, loc_cond.value, procs, None);
let then = from_can(env, loc_then.value, procs, None); let then = from_can(env, loc_then.value, procs, None);
let cond_symbol = env.fresh_symbol(); let branch_symbol = env.fresh_symbol();
let cond_expr = Expr::Cond { let cond_expr = Expr::Cond {
cond: cond_symbol, cond_symbol: branch_symbol,
branch_symbol,
cond_layout: cond_layout.clone(), cond_layout: cond_layout.clone(),
pass: env.arena.alloc(then), pass: (&[], env.arena.alloc(then)),
fail: env.arena.alloc(expr), fail: (&[], env.arena.alloc(expr)),
ret_layout: ret_layout.clone(), ret_layout: ret_layout.clone(),
}; };
expr = Expr::Store( expr = Expr::Store(
env.arena env.arena
.alloc(vec![(cond_symbol, Layout::Builtin(Builtin::Bool), cond)]), .alloc(vec![(branch_symbol, Layout::Builtin(Builtin::Bool), cond)]),
env.arena.alloc(cond_expr), env.arena.alloc(cond_expr),
); );
} }
@ -1070,7 +1080,7 @@ fn from_can_when<'a>(
let mut stores = Vec::with_capacity_in(1, env.arena); let mut stores = Vec::with_capacity_in(1, env.arena);
let (mono_guard, expr_with_stores) = match store_pattern( let (mono_guard, stores, expr) = match store_pattern(
env, env,
&mono_pattern, &mono_pattern,
cond_symbol, cond_symbol,
@ -1091,15 +1101,14 @@ fn from_can_when<'a>(
stores: stores.into_bump_slice(), stores: stores.into_bump_slice(),
expr, expr,
}, },
&[] as &[_],
mono_expr.clone(), mono_expr.clone(),
) )
} else { } else {
( (
crate::decision_tree::Guard::NoGuard, crate::decision_tree::Guard::NoGuard,
Expr::Store(
stores.into_bump_slice(), stores.into_bump_slice(),
env.arena.alloc(mono_expr.clone()), mono_expr.clone(),
),
) )
} }
} }
@ -1111,19 +1120,21 @@ fn from_can_when<'a>(
stores: &[], stores: &[],
expr: Expr::RuntimeError(env.arena.alloc(message)), expr: Expr::RuntimeError(env.arena.alloc(message)),
}, },
&[] as &[_],
// we can never hit this // we can never hit this
Expr::RuntimeError(&"invalid pattern with guard: unreachable"), Expr::RuntimeError(&"invalid pattern with guard: unreachable"),
) )
} else { } else {
( (
crate::decision_tree::Guard::NoGuard, crate::decision_tree::Guard::NoGuard,
&[] as &[_],
Expr::RuntimeError(env.arena.alloc(message)), Expr::RuntimeError(env.arena.alloc(message)),
) )
} }
} }
}; };
opt_branches.push((mono_pattern, mono_guard, expr_with_stores)); opt_branches.push((mono_pattern, mono_guard, stores, expr));
} }
} }

View file

@ -171,10 +171,11 @@ mod test_mono {
Expr::Bool(true), Expr::Bool(true),
)], )],
&Cond { &Cond {
cond: gen_symbol_0, cond_symbol: gen_symbol_0,
branch_symbol: gen_symbol_0,
cond_layout: Builtin(Bool), cond_layout: Builtin(Bool),
pass: &Expr::Str("bar"), pass: (&[] as &[_], &Expr::Str("bar")),
fail: &Expr::Str("foo"), fail: (&[] as &[_], &Expr::Str("foo")),
ret_layout: Builtin(Str), ret_layout: Builtin(Str),
}, },
) )
@ -208,23 +209,28 @@ mod test_mono {
Expr::Bool(true), Expr::Bool(true),
)], )],
&Cond { &Cond {
cond: gen_symbol_0, cond_symbol: gen_symbol_0,
branch_symbol: gen_symbol_0,
cond_layout: Builtin(Bool), cond_layout: Builtin(Bool),
pass: &Expr::Str("bar"), pass: (&[] as &[_], &Expr::Str("bar")),
fail: &Store( fail: (
&[] as &[_],
&Store(
&[( &[(
gen_symbol_1, gen_symbol_1,
Layout::Builtin(layout::Builtin::Bool), Layout::Builtin(layout::Builtin::Bool),
Expr::Bool(false), Expr::Bool(false),
)], )],
&Cond { &Cond {
cond: gen_symbol_1, cond_symbol: gen_symbol_1,
branch_symbol: gen_symbol_1,
cond_layout: Builtin(Bool), cond_layout: Builtin(Bool),
pass: &Expr::Str("foo"), pass: (&[] as &[_], &Expr::Str("foo")),
fail: &Expr::Str("baz"), fail: (&[] as &[_], &Expr::Str("baz")),
ret_layout: Builtin(Str), ret_layout: Builtin(Str),
}, },
), ),
),
ret_layout: Builtin(Str), ret_layout: Builtin(Str),
}, },
) )
@ -261,10 +267,11 @@ mod test_mono {
Expr::Bool(true), Expr::Bool(true),
)], )],
&Cond { &Cond {
cond: gen_symbol_0, cond_symbol: gen_symbol_0,
branch_symbol: gen_symbol_0,
cond_layout: Builtin(Bool), cond_layout: Builtin(Bool),
pass: &Expr::Str("bar"), pass: (&[] as &[_], &Expr::Str("bar")),
fail: &Expr::Str("foo"), fail: (&[] as &[_], &Expr::Str("foo")),
ret_layout: Builtin(Str), ret_layout: Builtin(Str),
}, },
), ),

View file

@ -114,14 +114,15 @@ mod test_opt {
} }
Cond { Cond {
cond: _, cond_symbol: _,
branch_symbol: _,
cond_layout: _, cond_layout: _,
pass, pass,
fail, fail,
ret_layout: _, ret_layout: _,
} => { } => {
extract_named_calls_help(pass, calls, unexpected_calls); extract_named_calls_help(pass.1, calls, unexpected_calls);
extract_named_calls_help(fail, calls, unexpected_calls); extract_named_calls_help(fail.1, calls, unexpected_calls);
} }
Switch { Switch {
cond, cond,
@ -131,9 +132,9 @@ mod test_opt {
ret_layout: _, ret_layout: _,
} => { } => {
extract_named_calls_help(cond, calls, unexpected_calls); extract_named_calls_help(cond, calls, unexpected_calls);
extract_named_calls_help(default_branch, calls, unexpected_calls); extract_named_calls_help(default_branch.1, calls, unexpected_calls);
for (_, branch_expr) in branches.iter() { for (_, _, branch_expr) in branches.iter() {
extract_named_calls_help(branch_expr, calls, unexpected_calls); extract_named_calls_help(branch_expr, calls, unexpected_calls);
} }
} }