perform copy propagation

This commit is contained in:
Folkert 2020-08-09 00:48:53 +02:00
parent 078c6df677
commit f8143e3e53
4 changed files with 581 additions and 121 deletions

View file

@ -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 {

View file

@ -93,7 +93,6 @@ pub fn occuring_variables_expr(expr: &Expr<'_>, result: &mut MutSet<Symbol>) {
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()),
}
}
}

View file

@ -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,23 +1884,11 @@ 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,
)
stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
.unwrap();
// convert the def body, store in outer_symbol
@ -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<Symbol, Symbol>, s: Symbol) -> Option<Symbol> {
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<Symbol, Symbol>,
) -> 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<Symbol, Symbol>,
) -> Option<Expr<'a>> {
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<Stmt<'a>, &'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));
}

View file

@ -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,26 +915,65 @@ 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;
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;
"#
),