Implement branching conditionals on Cranelift

This commit is contained in:
Richard Feldman 2020-01-10 18:15:01 -05:00
parent c4bd79372e
commit a4352be55f
5 changed files with 160 additions and 160 deletions

View file

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

View file

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

View file

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

View file

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

View file

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