mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 16:44:33 +00:00
Implement branching conditionals on Cranelift
This commit is contained in:
parent
c4bd79372e
commit
a4352be55f
5 changed files with 160 additions and 160 deletions
|
@ -2,7 +2,7 @@ use bumpalo::collections::Vec;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use cranelift::frontend::Switch;
|
use cranelift::frontend::Switch;
|
||||||
use cranelift::prelude::{
|
use cranelift::prelude::{
|
||||||
AbiParam, ExternalName, FunctionBuilder, FunctionBuilderContext, MemFlags,
|
AbiParam, ExternalName, FloatCC, FunctionBuilder, FunctionBuilderContext, IntCC, MemFlags,
|
||||||
};
|
};
|
||||||
use cranelift_codegen::ir::entities::{StackSlot, Value};
|
use cranelift_codegen::ir::entities::{StackSlot, Value};
|
||||||
use cranelift_codegen::ir::stackslot::{StackSlotData, StackSlotKind};
|
use cranelift_codegen::ir::stackslot::{StackSlotData, StackSlotKind};
|
||||||
|
@ -15,8 +15,8 @@ use inlinable_string::InlinableString;
|
||||||
use crate::collections::ImMap;
|
use crate::collections::ImMap;
|
||||||
use crate::crane::convert::{sig_from_layout, type_from_layout, type_from_var};
|
use crate::crane::convert::{sig_from_layout, type_from_layout, type_from_var};
|
||||||
use crate::mono::expr::{Expr, Proc, Procs};
|
use crate::mono::expr::{Expr, Proc, Procs};
|
||||||
use crate::mono::layout::Layout;
|
use crate::mono::layout::{Builtin, Layout};
|
||||||
use crate::subs::{Content, FlatType, Subs, Variable};
|
use crate::subs::{Subs, Variable};
|
||||||
|
|
||||||
type Scope = ImMap<InlinableString, ScopeEntry>;
|
type Scope = ImMap<InlinableString, ScopeEntry>;
|
||||||
|
|
||||||
|
@ -49,23 +49,25 @@ pub fn build_expr<'a, B: Backend>(
|
||||||
Float(num) => builder.ins().f64const(*num),
|
Float(num) => builder.ins().f64const(*num),
|
||||||
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_lhs,
|
cond_lhs,
|
||||||
// cond_rhs,
|
cond_rhs,
|
||||||
// pass,
|
pass,
|
||||||
// fail,
|
fail,
|
||||||
// ret_var,
|
cond_layout,
|
||||||
// } => {
|
ret_var,
|
||||||
// let cond = Cond2 {
|
} => {
|
||||||
// cond_lhs,
|
let branch = Branch2 {
|
||||||
// cond_rhs,
|
cond_lhs,
|
||||||
// pass,
|
cond_rhs,
|
||||||
// fail,
|
pass,
|
||||||
// ret_var: *ret_var,
|
fail,
|
||||||
// };
|
cond_layout,
|
||||||
|
ret_var: *ret_var,
|
||||||
|
};
|
||||||
|
|
||||||
// build_cond(env, scope, cond, procs)
|
build_branch2(env, scope, module, builder, branch, procs)
|
||||||
// }
|
}
|
||||||
Switch {
|
Switch {
|
||||||
cond,
|
cond,
|
||||||
branches,
|
branches,
|
||||||
|
@ -206,100 +208,91 @@ pub fn build_expr<'a, B: Backend>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// struct Cond2<'a> {
|
struct Branch2<'a> {
|
||||||
// cond_lhs: &'a Expr<'a>,
|
cond_lhs: &'a Expr<'a>,
|
||||||
// cond_rhs: &'a Expr<'a>,
|
cond_rhs: &'a Expr<'a>,
|
||||||
// pass: &'a Expr<'a>,
|
cond_layout: &'a Layout<'a>,
|
||||||
// fail: &'a Expr<'a>,
|
pass: &'a Expr<'a>,
|
||||||
// ret_var: Variable,
|
fail: &'a Expr<'a>,
|
||||||
// }
|
ret_var: Variable,
|
||||||
|
}
|
||||||
|
|
||||||
// fn build_cond<'a, 'ctx, 'env>(
|
fn build_branch2<'a, B: Backend>(
|
||||||
// env: &Env<'ctx, 'env>,
|
env: &Env<'a>,
|
||||||
// scope: &Scope<'ctx>,
|
scope: &Scope,
|
||||||
// parent: FunctionValue<'ctx>,
|
module: &mut Module<B>,
|
||||||
// cond: Cond2<'a>,
|
builder: &mut FunctionBuilder,
|
||||||
// procs: &Procs<'a>,
|
branch: Branch2<'a>,
|
||||||
// ) -> BasicValueEnum<'ctx> {
|
procs: &Procs<'a>,
|
||||||
// let builder = env.builder;
|
) -> Value {
|
||||||
// let context = env.context;
|
let subs = &env.subs;
|
||||||
// let subs = &env.subs;
|
let cfg = env.cfg;
|
||||||
|
|
||||||
// let content = subs.get_without_compacting(cond.ret_var).content;
|
// Declare a variable which each branch will mutate to be the value of that branch.
|
||||||
// let ret_type = type_from_content(&content, subs, context).unwrap_or_else(|err| {
|
// At the end of the expression, we will evaluate to this.
|
||||||
// panic!(
|
let ret_type = type_from_var(branch.ret_var, subs, cfg);
|
||||||
// "Error converting cond branch ret_type content {:?} to basic type: {:?}",
|
let ret = cranelift::frontend::Variable::with_u32(0);
|
||||||
// cond.pass, err
|
|
||||||
// )
|
|
||||||
// });
|
|
||||||
|
|
||||||
// let lhs = build_expr(env, scope, cond.cond_lhs, procs);
|
// The block we'll jump to once the switch has completed.
|
||||||
// let rhs = build_expr(env, scope, cond.cond_rhs, procs);
|
let ret_block = builder.create_ebb();
|
||||||
|
|
||||||
// match (lhs_layout, rhs_layout) {
|
builder.declare_var(ret, ret_type);
|
||||||
// // TODO do this based on lhs_type and rhs_type
|
|
||||||
// (Layout::Float64, Layout::Float64) => {
|
|
||||||
// let then_ebb = builder.create_ebb();
|
|
||||||
// builder.switch_to_block(then_ebb);
|
|
||||||
|
|
||||||
// let else_ebb = builder.create_ebb();
|
let lhs = build_expr(env, scope, module, builder, branch.cond_lhs, procs);
|
||||||
// builder.switch_to_block(else_ebb);
|
let rhs = build_expr(env, scope, module, builder, branch.cond_rhs, procs);
|
||||||
|
let pass_block = builder.create_ebb();
|
||||||
|
let fail_block = builder.create_ebb();
|
||||||
|
|
||||||
// // build branch
|
match branch.cond_layout {
|
||||||
// let then_bb = context.append_basic_block("then");
|
Layout::Builtin(Builtin::Float64) => {
|
||||||
// let else_bb = context.append_basic_block("else");
|
// For floats, first do a `fcmp` comparison to get a bool answer about equality,
|
||||||
// let cont_bb = context.append_basic_block("branchcont");
|
// then use `brnz` to branch if that bool equality answer was nonzero (aka true).
|
||||||
|
let is_eq = builder.ins().fcmp(FloatCC::Equal, lhs, rhs);
|
||||||
|
|
||||||
// builder.build_conditional_branch(comparison, &then_bb, &else_bb);
|
builder.ins().brnz(is_eq, pass_block, &[]);
|
||||||
|
}
|
||||||
|
Layout::Builtin(Builtin::Int64) => {
|
||||||
|
// For ints, we can compare and branch in the same instruction: `icmp`
|
||||||
|
builder
|
||||||
|
.ins()
|
||||||
|
.br_icmp(IntCC::Equal, lhs, rhs, pass_block, &[]);
|
||||||
|
}
|
||||||
|
other => panic!("I don't know how to build a conditional for {:?}", other),
|
||||||
|
}
|
||||||
|
|
||||||
// // build then block
|
// Unconditionally jump to fail_block (if we didn't just jump to pass_block).
|
||||||
// builder.position_at_end(&then_bb);
|
builder.ins().jump(fail_block, &[]);
|
||||||
// let then_val = build_expr(env, scope, pass, procs);
|
|
||||||
// builder.build_unconditional_branch(&cont_bb);
|
|
||||||
|
|
||||||
// let then_bb = builder.get_insert_block().unwrap();
|
let mut build_branch = |expr, block| {
|
||||||
|
builder.switch_to_block(block);
|
||||||
|
|
||||||
// // build else block
|
// TODO re-enable this once Switch stops making unsealed
|
||||||
// builder.position_at_end(&else_bb);
|
// EBBs, e.g. https://docs.rs/cranelift-frontend/0.52.0/src/cranelift_frontend/switch.rs.html#143
|
||||||
// let else_val = build_expr(env, scope, fail, procs);
|
// builder.seal_block(block);
|
||||||
// builder.build_unconditional_branch(&cont_bb);
|
|
||||||
|
|
||||||
// let else_bb = builder.get_insert_block().unwrap();
|
// Mutate the ret variable to be the outcome of this branch.
|
||||||
|
let value = build_expr(env, scope, module, builder, expr, procs);
|
||||||
|
|
||||||
// // emit merge block
|
builder.def_var(ret, value);
|
||||||
// builder.position_at_end(&cont_bb);
|
|
||||||
|
|
||||||
// let phi = builder.build_phi(ret_type, "branch");
|
// Unconditionally jump to ret_block, making the whole expression evaluate to ret.
|
||||||
|
builder.ins().jump(ret_block, &[]);
|
||||||
|
};
|
||||||
|
|
||||||
// phi.add_incoming(&[
|
build_branch(branch.pass, pass_block);
|
||||||
// (&Into::<BasicValueEnum>::into(then_val), &then_bb),
|
build_branch(branch.fail, fail_block);
|
||||||
// (&Into::<BasicValueEnum>::into(else_val), &else_bb),
|
|
||||||
// ]);
|
|
||||||
|
|
||||||
// phi.as_basic_value()
|
// Finally, build ret_block - which contains our terminator instruction.
|
||||||
|
{
|
||||||
|
builder.switch_to_block(ret_block);
|
||||||
|
// TODO re-enable this once Switch stops making unsealed
|
||||||
|
// EBBs, e.g. https://docs.rs/cranelift-frontend/0.52.0/src/cranelift_frontend/switch.rs.html#143
|
||||||
|
// builder.seal_block(block);
|
||||||
|
|
||||||
// builder.ins().fcmp(FloatCC::Equal, lhs, rhs, ebb, &[]);
|
// Now that ret has been mutated by the switch statement, evaluate to it.
|
||||||
|
builder.use_var(ret)
|
||||||
// build_phi2(
|
}
|
||||||
// env, scope, comparison, cond.pass, cond.fail, ret_type, procs,
|
}
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
// (Layout::Int64, Layout::Int64) => {
|
|
||||||
// let ebb = builder.create_ebb();
|
|
||||||
|
|
||||||
// builder.ins().icmp(IntCC::Equal, lhs, rhs, ebb, &[]);
|
|
||||||
|
|
||||||
// build_phi2(
|
|
||||||
// env, scope, comparison, cond.pass, cond.fail, ret_type, procs,
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// _ => panic!(
|
|
||||||
// "Tried to make a branch out of incompatible conditions: lhs = {:?} and rhs = {:?}",
|
|
||||||
// cond.cond_lhs, cond.cond_rhs
|
|
||||||
// ),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
struct SwitchArgs<'a> {
|
struct SwitchArgs<'a> {
|
||||||
pub cond_expr: &'a Expr<'a>,
|
pub cond_expr: &'a Expr<'a>,
|
||||||
pub cond_var: Variable,
|
pub cond_var: Variable,
|
||||||
|
@ -321,9 +314,9 @@ fn build_switch<'a, B: Backend>(
|
||||||
let SwitchArgs {
|
let SwitchArgs {
|
||||||
branches,
|
branches,
|
||||||
cond_expr,
|
cond_expr,
|
||||||
cond_var,
|
|
||||||
default_branch,
|
default_branch,
|
||||||
ret_type,
|
ret_type,
|
||||||
|
..
|
||||||
} = switch_args;
|
} = switch_args;
|
||||||
let mut blocks = Vec::with_capacity_in(branches.len(), env.arena);
|
let mut blocks = Vec::with_capacity_in(branches.len(), env.arena);
|
||||||
|
|
||||||
|
@ -352,38 +345,6 @@ fn build_switch<'a, B: Backend>(
|
||||||
// Run the switch. Each branch will mutate ret and then jump to ret_ebb.
|
// Run the switch. Each branch will mutate ret and then jump to ret_ebb.
|
||||||
let cond = build_expr(env, scope, module, builder, cond_expr, procs);
|
let cond = build_expr(env, scope, module, builder, cond_expr, procs);
|
||||||
|
|
||||||
// If necessary, convert cond from Float to Int using a bitcast.
|
|
||||||
let cond = match subs.get_without_compacting(cond_var).content {
|
|
||||||
Content::Structure(FlatType::Apply {
|
|
||||||
module_name,
|
|
||||||
name,
|
|
||||||
args,
|
|
||||||
}) if module_name.as_str() == crate::types::MOD_NUM
|
|
||||||
&& name.as_str() == crate::types::TYPE_NUM =>
|
|
||||||
{
|
|
||||||
let arg = *args.iter().next().unwrap();
|
|
||||||
|
|
||||||
match subs.get_without_compacting(arg).content {
|
|
||||||
Content::Structure(FlatType::Apply {
|
|
||||||
module_name, name, ..
|
|
||||||
}) if module_name.as_str() == crate::types::MOD_FLOAT => {
|
|
||||||
debug_assert!(name.as_str() == crate::types::TYPE_FLOATINGPOINT);
|
|
||||||
|
|
||||||
// This is an f64, but switch only works on u64.
|
|
||||||
//
|
|
||||||
// Since it's the same size, we could theoretically use raw_bitcast
|
|
||||||
// which doesn't actually change the bits, just allows
|
|
||||||
// them to be used as a different type from its register.
|
|
||||||
//
|
|
||||||
// However, in practice, this fails Cranelift verification.
|
|
||||||
builder.ins().bitcast(types::I64, cond)
|
|
||||||
}
|
|
||||||
_ => cond,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
other => panic!("Cannot Switch on type {:?}", other),
|
|
||||||
};
|
|
||||||
|
|
||||||
switch.emit(builder, cond, default_block);
|
switch.emit(builder, cond, default_block);
|
||||||
|
|
||||||
let mut build_branch = |block, expr| {
|
let mut build_branch = |block, expr| {
|
||||||
|
|
|
@ -47,6 +47,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
||||||
pass,
|
pass,
|
||||||
fail,
|
fail,
|
||||||
ret_var,
|
ret_var,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
let cond = Cond2 {
|
let cond = Cond2 {
|
||||||
cond_lhs,
|
cond_lhs,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::can;
|
use crate::can;
|
||||||
use crate::can::pattern::Pattern;
|
use crate::can::pattern::Pattern;
|
||||||
use crate::collections::MutMap;
|
use crate::collections::MutMap;
|
||||||
use crate::mono::layout::Layout;
|
use crate::mono::layout::{Builtin, Layout};
|
||||||
use crate::region::Located;
|
use crate::region::Located;
|
||||||
use crate::subs::{Content, FlatType, Subs, Variable};
|
use crate::subs::{Content, FlatType, Subs, Variable};
|
||||||
use bumpalo::collections::Vec;
|
use bumpalo::collections::Vec;
|
||||||
|
@ -55,6 +55,7 @@ pub enum Expr<'a> {
|
||||||
// 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_lhs: &'a Expr<'a>,
|
cond_lhs: &'a Expr<'a>,
|
||||||
cond_rhs: &'a Expr<'a>,
|
cond_rhs: &'a Expr<'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: &'a Expr<'a>,
|
||||||
fail: &'a Expr<'a>,
|
fail: &'a Expr<'a>,
|
||||||
|
@ -351,6 +352,7 @@ fn from_can_when<'a>(
|
||||||
let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
|
let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
|
||||||
|
|
||||||
Expr::Cond {
|
Expr::Cond {
|
||||||
|
cond_layout: Layout::Builtin(Builtin::Int64),
|
||||||
cond_lhs,
|
cond_lhs,
|
||||||
cond_rhs,
|
cond_rhs,
|
||||||
pass,
|
pass,
|
||||||
|
@ -365,6 +367,7 @@ fn from_can_when<'a>(
|
||||||
let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
|
let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
|
||||||
|
|
||||||
Expr::Cond {
|
Expr::Cond {
|
||||||
|
cond_layout: Layout::Builtin(Builtin::Float64),
|
||||||
cond_lhs,
|
cond_lhs,
|
||||||
cond_rhs,
|
cond_rhs,
|
||||||
pass,
|
pass,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::subs::{Content, FlatType, Subs};
|
use crate::subs::{Content, FlatType, Subs, Variable};
|
||||||
use crate::types;
|
use crate::types;
|
||||||
use bumpalo::collections::Vec;
|
use bumpalo::collections::Vec;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
|
@ -25,6 +25,11 @@ pub enum Builtin<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Layout<'a> {
|
impl<'a> Layout<'a> {
|
||||||
|
pub fn from_var(arena: &'a Bump, var: Variable, subs: &Subs) -> Result<Self, ()> {
|
||||||
|
let content = subs.get_without_compacting(var).content;
|
||||||
|
|
||||||
|
Self::from_content(arena, content, subs)
|
||||||
|
}
|
||||||
/// Returns Err(()) if given an error, or Ok(Layout) if given a non-erroneous Structure.
|
/// Returns Err(()) if given an error, or Ok(Layout) if given a non-erroneous Structure.
|
||||||
/// Panics if given a FlexVar or RigidVar, since those should have been
|
/// Panics if given a FlexVar or RigidVar, since those should have been
|
||||||
/// monomorphized away already!
|
/// monomorphized away already!
|
||||||
|
|
|
@ -167,35 +167,65 @@ mod test_crane {
|
||||||
assert_evals_to!("1234.0", 1234.0, f64);
|
assert_evals_to!("1234.0", 1234.0, f64);
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn gen_when_take_first_branch() {
|
fn branch_first_float() {
|
||||||
// assert_evals_to!(
|
assert_evals_to!(
|
||||||
// indoc!(
|
indoc!(
|
||||||
// r#"
|
r#"
|
||||||
// when 1 is
|
when 1.23 is
|
||||||
// 1 -> 12
|
1.23 -> 12
|
||||||
// _ -> 34
|
_ -> 34
|
||||||
// "#
|
"#
|
||||||
// ),
|
),
|
||||||
// 12,
|
12,
|
||||||
// i64
|
i64
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn gen_when_take_second_branch() {
|
fn branch_second_float() {
|
||||||
// assert_evals_to!(
|
assert_evals_to!(
|
||||||
// indoc!(
|
indoc!(
|
||||||
// r#"
|
r#"
|
||||||
// when 2 is
|
when 2.34 is
|
||||||
// 1 -> 63
|
1.23 -> 63
|
||||||
// _ -> 48
|
_ -> 48
|
||||||
// "#
|
"#
|
||||||
// ),
|
),
|
||||||
// 48,
|
48,
|
||||||
// i64
|
i64
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn branch_first_int() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
when 1 is
|
||||||
|
1 -> 12
|
||||||
|
_ -> 34
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
12,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn branch_second_int() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
when 2 is
|
||||||
|
1 -> 63
|
||||||
|
_ -> 48
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
48,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn gen_when_one_branch() {
|
fn gen_when_one_branch() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue