Get Switch running on Crane

This commit is contained in:
Richard Feldman 2020-01-10 01:20:11 -05:00
parent 750a9f9ca3
commit e49b608157
6 changed files with 568 additions and 281 deletions

View file

@ -1,4 +1,6 @@
use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use cranelift::frontend::Switch;
use cranelift::prelude::{ use cranelift::prelude::{
AbiParam, ExternalName, FunctionBuilder, FunctionBuilderContext, MemFlags, AbiParam, ExternalName, FunctionBuilder, FunctionBuilderContext, MemFlags,
}; };
@ -11,10 +13,10 @@ use cranelift_module::{Backend, FuncId, Linkage, Module};
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
use crate::collections::ImMap; use crate::collections::ImMap;
use crate::crane::convert::{content_to_crane_type, sig_from_layout, type_from_layout}; 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::Layout;
use crate::subs::Subs; use crate::subs::{Content, FlatType, Subs, Variable};
type Scope = ImMap<InlinableString, ScopeEntry>; type Scope = ImMap<InlinableString, ScopeEntry>;
@ -64,8 +66,23 @@ pub fn build_expr<'a, B: Backend>(
// build_cond(env, scope, cond, procs) // build_cond(env, scope, cond, procs)
// } // }
Branches { .. } => { Switch {
panic!("TODO build_branches(env, scope, cond_lhs, branches, procs)"); cond,
branches,
default_branch,
ret_var,
cond_var,
} => {
let ret_type = type_from_var(*ret_var, &env.subs, env.cfg);
let switch_args = SwitchArgs {
cond_var: *cond_var,
cond_expr: cond,
branches: branches,
default_branch,
ret_type,
};
build_switch(env, scope, module, builder, switch_args, procs)
} }
Store(ref stores, ref ret) => { Store(ref stores, ref ret) => {
let mut scope = im_rc::HashMap::clone(scope); let mut scope = im_rc::HashMap::clone(scope);
@ -77,7 +94,7 @@ pub fn build_expr<'a, B: Backend>(
let val = build_expr(env, &scope, module, builder, &expr, procs); let val = build_expr(env, &scope, module, builder, &expr, procs);
let content = subs.get_without_compacting(*var).content; let content = subs.get_without_compacting(*var).content;
let layout = Layout::from_content(arena, content, subs) let layout = Layout::from_content(arena, content, subs)
.unwrap_or_else(|()| panic!("TODO generate a runtime error here!")); .unwrap_or_else(|()| panic!("TODO generate a runtime error for this Store!"));
let expr_type = type_from_layout(cfg, &layout, subs); let expr_type = type_from_layout(cfg, &layout, subs);
let slot = builder.create_stack_slot(StackSlotData::new( let slot = builder.create_stack_slot(StackSlotData::new(
@ -110,7 +127,7 @@ pub fn build_expr<'a, B: Backend>(
} else if name == "Bool.and" { } else if name == "Bool.and" {
panic!("TODO create a branch for &&"); panic!("TODO create a branch for &&");
} else { } else {
let mut arg_vals = Vec::with_capacity(args.len()); let mut arg_vals = Vec::with_capacity_in(args.len(), env.arena);
for arg in args.iter() { for arg in args.iter() {
arg_vals.push(build_expr(env, scope, module, builder, arg, procs)); arg_vals.push(build_expr(env, scope, module, builder, arg, procs));
@ -147,7 +164,7 @@ pub fn build_expr<'a, B: Backend>(
} }
CallByPointer(ref sub_expr, ref args, ref fn_var) => { CallByPointer(ref sub_expr, ref args, ref fn_var) => {
let subs = &env.subs; let subs = &env.subs;
let mut arg_vals = Vec::with_capacity(args.len()); let mut arg_vals = Vec::with_capacity_in(args.len(), env.arena);
for arg in args.iter() { for arg in args.iter() {
arg_vals.push(build_expr(env, scope, module, builder, arg, procs)); arg_vals.push(build_expr(env, scope, module, builder, arg, procs));
@ -204,97 +221,29 @@ pub fn build_expr<'a, B: Backend>(
// cond: Cond2<'a>, // cond: Cond2<'a>,
// procs: &Procs<'a>, // procs: &Procs<'a>,
// ) -> BasicValueEnum<'ctx> { // ) -> BasicValueEnum<'ctx> {
// let builder = env.builder;
// let context = env.context;
// let subs = &env.subs;
// let content = subs.get_without_compacting(cond.ret_var).content;
// let ret_type = content_to_crane_type(&content, subs, context).unwrap_or_else(|err| {
// panic!(
// "Error converting cond branch ret_type content {:?} to basic type: {:?}",
// cond.pass, err
// )
// });
// let lhs = build_expr(env, scope, cond.cond_lhs, procs);
// let rhs = build_expr(env, scope, cond.cond_rhs, procs);
// match (lhs, rhs) {
// (FloatValue(lhs_float), FloatValue(rhs_float)) => {
// let comparison =
// builder.build_float_compare(FloatPredicate::OEQ, lhs_float, rhs_float, "cond");
// build_phi2(
// env, scope, comparison, cond.pass, cond.fail, ret_type, procs,
// )
// }
// (IntValue(lhs_int), IntValue(rhs_int)) => {
// let comparison = builder.build_int_compare(IntPredicate::EQ, lhs_int, rhs_int, "cond");
// 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
// ),
// }
// }
// fn build_branches<'a, 'ctx, 'env>(
// env: &Env<'ctx, 'env>,
// scope: &Scope<'ctx>,
// parent: FunctionValue<'ctx>,
// cond_lhs: &'a Expr<'a>,
// branches: &'a [(Expr<'a>, Expr<'a>, Expr<'a>)],
// ret_type: BasicValueEnum<'ctx>,
// procs: &Procs<'a, 'ctx>,
// ) -> BasicValueEnum<'ctx> {
// let builder = env.builder; // let builder = env.builder;
// let context = env.context; // let context = env.context;
// let lhs = build_expr(env, scope, cond_lhs, procs); // let subs = &env.subs;
// let mut branch_iter = branches.into_iter();
// let content = subs.get_without_compacting(cond.ret_var).content; // let content = subs.get_without_compacting(cond.ret_var).content;
// let ret_type = content_to_crane_type(&content, subs, context).unwrap_or_else(|err| { // let ret_type = type_from_content(&content, subs, context).unwrap_or_else(|err| {
// panic!( // panic!(
// "Error converting cond branch ret_type content {:?} to basic type: {:?}", // "Error converting cond branch ret_type content {:?} to basic type: {:?}",
// cond.pass, err // cond.pass, err
// ) // )
// }); // });
// for (cond_rhs, cond_pass, cond_else) in branches { // let lhs = build_expr(env, scope, cond.cond_lhs, procs);
// let rhs = build_expr(env, scope, cond_rhs, procs); // let rhs = build_expr(env, scope, cond.cond_rhs, procs);
// let pass = build_expr(env, scope, cond_pass, procs);
// let fail = build_expr(env, scope, cond_else, procs);
// let cond = Cond { // match (lhs_layout, rhs_layout) {
// lhs, // // TODO do this based on lhs_type and rhs_type
// rhs, // (Layout::Float64, Layout::Float64) => {
// pass, // let then_ebb = builder.create_ebb();
// fail, // builder.switch_to_block(then_ebb);
// ret_type,
// };
// build_cond(env, scope, cond, procs) // let else_ebb = builder.create_ebb();
// } // builder.switch_to_block(else_ebb);
// }
// TODO trim down these arguments
// #[allow(clippy::too_many_arguments)]
// fn build_phi2<'a, 'ctx, 'env>(
// env: &Env<'ctx, 'env>,
// scope: &Scope<'ctx>,
// parent: FunctionValue<'ctx>,
// comparison: IntValue<'ctx>,
// pass: &'a Expr<'a>,
// fail: &'a Expr<'a>,
// ret_type: BasicTypeEnum<'ctx>,
// procs: &Procs<'a>,
// ) -> BasicValueEnum<'ctx> {
// let builder = env.builder;
// let context = env.context;
// // build branch // // build branch
// let then_bb = context.append_basic_block("then"); // let then_bb = context.append_basic_block("then");
@ -328,7 +277,149 @@ pub fn build_expr<'a, B: Backend>(
// ]); // ]);
// phi.as_basic_value() // phi.as_basic_value()
// builder.ins().fcmp(FloatCC::Equal, lhs, rhs, ebb, &[]);
// 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> {
pub cond_expr: &'a Expr<'a>,
pub cond_var: Variable,
pub branches: &'a [(u64, Expr<'a>)],
pub default_branch: &'a Expr<'a>,
pub ret_type: Type,
}
fn build_switch<'a, B: Backend>(
env: &Env<'a>,
scope: &Scope,
module: &mut Module<B>,
builder: &mut FunctionBuilder,
switch_args: SwitchArgs<'a>,
procs: &Procs<'a>,
) -> Value {
let subs = &env.subs;
let mut switch = Switch::new();
let SwitchArgs {
branches,
cond_expr,
cond_var,
default_branch,
ret_type,
} = switch_args;
let mut blocks = Vec::with_capacity_in(branches.len(), env.arena);
// Declare a variable which each branch will mutate to be the value of that branch.
// At the end of the expression, we will evaluate to this.
let ret = cranelift::frontend::Variable::with_u32(0);
builder.declare_var(ret, ret_type);
// The block for the conditional's default branch.
let default_block = builder.create_ebb();
// The block we'll jump to once the switch has completed.
let ret_block = builder.create_ebb();
// Build the blocks for each branch, and register them in the switch.
// Do this before emitting the switch, because it needs to be emitted at the front.
for (int, _) in branches {
let block = builder.create_ebb();
blocks.push(block);
switch.set_entry(*int, block);
}
// 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);
// 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);
let mut build_branch = |block, expr| {
builder.switch_to_block(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);
// Mutate the ret variable to be the outcome of this branch.
let value = build_expr(env, scope, module, builder, expr, procs);
builder.def_var(ret, value);
// Unconditionally jump to ret_block, making the whole expression evaluate to ret.
builder.ins().jump(ret_block, &[]);
};
// Build the blocks for each branch
for ((_, expr), block) in branches.iter().zip(blocks) {
build_branch(block, expr);
}
// Build the block for the default branch
build_branch(default_block, default_branch);
// 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);
// Now that ret has been mutated by the switch statement, evaluate to it.
builder.use_var(ret)
}
}
pub fn declare_proc<'a, B: Backend>( pub fn declare_proc<'a, B: Backend>(
env: &Env<'a>, env: &Env<'a>,
@ -339,14 +430,8 @@ pub fn declare_proc<'a, B: Backend>(
let args = proc.args; let args = proc.args;
let subs = &env.subs; let subs = &env.subs;
let cfg = env.cfg; let cfg = env.cfg;
let ret_content = subs.get_without_compacting(proc.ret_var).content; // TODO this rtype_from_var is duplicated when building this Proc
// TODO this content_to_crane_type is duplicated when building this Proc let ret_type = type_from_var(proc.ret_var, subs, env.cfg);
let ret_type = content_to_crane_type(&ret_content, subs, env.cfg).unwrap_or_else(|err| {
panic!(
"Error converting function return value content to basic type: {:?}",
err
)
});
// Create a signature for the function // Create a signature for the function
let mut sig = module.make_signature(); let mut sig = module.make_signature();
@ -396,15 +481,15 @@ pub fn define_proc_body<'a, B: Backend>(
let mut func_ctx = FunctionBuilderContext::new(); let mut func_ctx = FunctionBuilderContext::new();
let mut builder: FunctionBuilder = FunctionBuilder::new(&mut ctx.func, &mut func_ctx); let mut builder: FunctionBuilder = FunctionBuilder::new(&mut ctx.func, &mut func_ctx);
let ebb = builder.create_ebb(); let block = builder.create_ebb();
builder.switch_to_block(ebb); builder.switch_to_block(block);
builder.append_ebb_params_for_function_params(ebb); builder.append_ebb_params_for_function_params(block);
// Add args to scope // Add args to scope
for (&param, (_, arg_name, var)) in builder.ebb_params(ebb).iter().zip(args) { for (&param, (_, arg_name, var)) in builder.ebb_params(block).iter().zip(args) {
let content = subs.get_without_compacting(*var).content; let content = subs.get_without_compacting(*var).content;
// TODO this content_to_crane_type is duplicated when building this Proc // TODO this type_from_content is duplicated when building this Proc
// //
let layout = Layout::from_content(arena, content, subs) let layout = Layout::from_content(arena, content, subs)
.unwrap_or_else(|()| panic!("TODO generate a runtime error here!")); .unwrap_or_else(|()| panic!("TODO generate a runtime error here!"));
@ -416,7 +501,11 @@ pub fn define_proc_body<'a, B: Backend>(
let body = build_expr(env, &scope, module, &mut builder, &proc.body, procs); let body = build_expr(env, &scope, module, &mut builder, &proc.body, procs);
builder.ins().return_(&[body]); builder.ins().return_(&[body]);
// 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.seal_all_blocks(); builder.seal_all_blocks();
builder.finalize(); builder.finalize();
} }

View file

@ -4,14 +4,16 @@ use cranelift_codegen::isa::TargetFrontendConfig;
use crate::mono::layout::Layout; use crate::mono::layout::Layout;
use crate::subs::FlatType::*; use crate::subs::FlatType::*;
use crate::subs::{Content, Subs}; use crate::subs::{Content, Subs, Variable};
use cranelift_module::{Backend, Module}; use cranelift_module::{Backend, Module};
pub fn content_to_crane_type( pub fn type_from_var(var: Variable, subs: &Subs, cfg: TargetFrontendConfig) -> Type {
content: &Content, let content = subs.get_without_compacting(var).content;
subs: &Subs,
cfg: TargetFrontendConfig, type_from_content(&content, subs, cfg)
) -> Result<Type, String> { }
pub fn type_from_content(content: &Content, subs: &Subs, cfg: TargetFrontendConfig) -> Type {
match content { match content {
Content::Structure(flat_type) => match flat_type { Content::Structure(flat_type) => match flat_type {
Apply { Apply {
@ -29,19 +31,19 @@ pub fn content_to_crane_type(
num_to_crane_type(arg_content) num_to_crane_type(arg_content)
} else { } else {
panic!( panic!(
"TODO handle content_to_crane_type for FlatType::Apply of {}.{} with args {:?}", "TODO handle type_from_content for FlatType::Apply of {}.{} with args {:?}",
module_name, name, args module_name, name, args
); );
} }
} }
Func(_, _) => Ok(cfg.pointer_type()), Func(_, _) => cfg.pointer_type(),
other => panic!("TODO handle content_to_crane_type for {:?}", other), other => panic!("TODO handle type_from_content for {:?}", other),
}, },
other => Err(format!("Cannot convert {:?} to BasicTypeEnum", other)), other => panic!("Cannot convert {:?} to Crane Type", other),
} }
} }
fn num_to_crane_type(content: Content) -> Result<Type, String> { fn num_to_crane_type(content: Content) -> Type {
match content { match content {
Content::Structure(flat_type) => match flat_type { Content::Structure(flat_type) => match flat_type {
Apply { Apply {
@ -57,18 +59,18 @@ fn num_to_crane_type(content: Content) -> Result<Type, String> {
&& args.is_empty() && args.is_empty()
{ {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(types::F64) types::F64
} else if module_name == crate::types::MOD_INT } else if module_name == crate::types::MOD_INT
&& name == crate::types::TYPE_INTEGER && name == crate::types::TYPE_INTEGER
&& args.is_empty() && args.is_empty()
{ {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(types::I64) types::I64
} else { } else {
Err(format!( panic!(
"Unrecognized numeric type: {}.{} with args {:?}", "Unrecognized numeric type: {}.{} with args {:?}",
module_name, name, args module_name, name, args
)) )
} }
} }
other => panic!( other => panic!(

View file

@ -64,9 +64,23 @@ pub enum Expr<'a> {
Branches { Branches {
/// The left-hand side of the conditional. We compile this to LLVM once, /// The left-hand side of the conditional. We compile this to LLVM once,
/// then reuse it to test against each different compiled cond_rhs value. /// then reuse it to test against each different compiled cond_rhs value.
cond_lhs: &'a Expr<'a>, cond: &'a Expr<'a>,
/// ( cond_rhs, pass, fail ) /// ( cond_rhs, pass, fail )
branches: &'a [(Expr<'a>, Expr<'a>, Expr<'a>)], branches: &'a [(Expr<'a>, Expr<'a>, Expr<'a>)],
default: &'a Expr<'a>,
ret_var: Variable,
},
/// Conditional branches for integers. These are more efficient.
Switch {
/// This *must* be an integer, because Switch potentially compiles to a jump table.
cond: &'a Expr<'a>,
cond_var: Variable,
/// The u64 in the tuple will be compared directly to the condition Expr.
/// If they are equal, this branch will be taken.
branches: &'a [(u64, Expr<'a>)],
/// If no other branches pass, this default branch will be taken.
default_branch: &'a Expr<'a>,
/// Each branch must return a value of this type.
ret_var: Variable, ret_var: Variable,
}, },
Tag { Tag {
@ -193,77 +207,172 @@ fn from_can<'a>(
loc_cond, loc_cond,
branches, branches,
} => { } => {
debug_assert!(!branches.is_empty()); match branches.len() {
0 => {
// A when-expression with no branches is a runtime error.
// We can't know what to return!
panic!("TODO compile a 0-branch when-expression to a RuntimeError");
}
1 => {
// A when-expression with exactly 1 branch is essentially a LetNonRec.
// As such, we can compile it direcly to a Store.
let arena = env.arena;
let mut stored = Vec::with_capacity_in(1, arena);
let (loc_pattern, loc_branch) = branches.into_iter().next().unwrap();
if branches.len() == 2 { store_pattern(
let arena = env.arena; env,
let mut iter = branches.into_iter(); loc_pattern.value,
let (loc_pat1, loc_then) = iter.next().unwrap(); loc_cond.value,
let (loc_pat2, loc_else) = iter.next().unwrap(); cond_var,
procs,
&mut stored,
);
match (&loc_pat1.value, &loc_pat2.value) { let ret = from_can(env, loc_branch.value, procs, None);
(IntLiteral(int), IntLiteral(_)) | (IntLiteral(int), Underscore) => {
let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None));
let cond_rhs = arena.alloc(Expr::Int(*int));
let pass = arena.alloc(from_can(env, loc_then.value, procs, None));
let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
Expr::Cond { Expr::Store(stored.into_bump_slice(), arena.alloc(ret))
cond_lhs, }
cond_rhs, 2 => {
pass, // A when-expression with exactly 2 branches compiles to a Cond.
fail, let arena = env.arena;
ret_var: expr_var, let mut iter = branches.into_iter();
let (loc_pat1, loc_then) = iter.next().unwrap();
let (loc_pat2, loc_else) = iter.next().unwrap();
match (&loc_pat1.value, &loc_pat2.value) {
(IntLiteral(int), IntLiteral(_)) | (IntLiteral(int), Underscore) => {
let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None));
let cond_rhs = arena.alloc(Expr::Int(*int));
let pass = arena.alloc(from_can(env, loc_then.value, procs, None));
let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
Expr::Cond {
cond_lhs,
cond_rhs,
pass,
fail,
ret_var: expr_var,
}
} }
} (FloatLiteral(float), FloatLiteral(_))
(FloatLiteral(float), FloatLiteral(_)) | (FloatLiteral(float), Underscore) => { | (FloatLiteral(float), Underscore) => {
let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None)); let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None));
let cond_rhs = arena.alloc(Expr::Float(*float)); let cond_rhs = arena.alloc(Expr::Float(*float));
let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); let pass = arena.alloc(from_can(env, loc_then.value, procs, None));
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_lhs, cond_lhs,
cond_rhs, cond_rhs,
pass, pass,
fail, fail,
ret_var: expr_var, ret_var: expr_var,
}
}
_ => {
panic!("TODO handle more conds");
} }
}
_ => {
panic!("TODO handle more conds");
} }
} }
} else if branches.len() == 1 { _ => {
// A when-expression with exactly 1 branch is essentially a LetNonRec. // This is a when-expression with 3+ branches.
// As such, we can compile it direcly to a Store. let arena = env.arena;
let arena = env.arena; let cond = from_can(env, loc_cond.value, procs, None);
let mut stored = Vec::with_capacity_in(1, arena); let content = env.subs.get_without_compacting(cond_var).content;
let (loc_pattern, loc_branch) = branches.into_iter().next().unwrap();
store_pattern( // If the condition is an Int or Float, we can potentially use
env, // a Switch for more efficiency.
loc_pattern.value, if content.is_number() {
loc_cond.value, // These are integer literals or underscore patterns,
cond_var, // so they're eligible for user in a jump table.
procs, let mut jumpable_branches = Vec::with_capacity_in(branches.len(), arena);
&mut stored, let mut opt_default_branch = None;
);
let ret = from_can(env, loc_branch.value, procs, None); for (loc_pat, loc_expr) in branches {
let mono_expr = from_can(env, loc_expr.value, procs, None);
Expr::Store(stored.into_bump_slice(), arena.alloc(ret)) match &loc_pat.value {
} else { IntLiteral(int) => {
// /// More than two conditional branches, e.g. a 3-way when-expression // Switch only compares the condition to the
// Expr::Branches { // alternatives based on their bit patterns,
// /// The left-hand side of the conditional. We compile this to LLVM once, // so casting from i64 to u64 makes no difference here.
// /// then reuse it to test against each different compiled cond_rhs value. jumpable_branches.push((*int as u64, mono_expr));
// cond_lhs: &'a Expr<'a>, }
// /// ( cond_rhs, pass, fail ) FloatLiteral(float) => {
// branches: &'a [(Expr<'a>, Expr<'a>, Expr<'a>)], // Switch only compares the condition to the
// ret_var: Variable, // alternatives based on their bit patterns,
// }, // so casting from f64 to u64 makes no difference here.
panic!("TODO support when-expressions of more than 2 branches."); jumpable_branches.push((*float as u64, mono_expr));
}
Identifier(_symbol) => {
// Since this is an ident, it must be
// the last pattern in the `when`.
// We can safely treat this like an `_`
// except that we need to wrap this branch
// in a `Store` so the identifier is in scope!
opt_default_branch = Some(arena.alloc(if true {
// Using `if true` for this TODO panic to avoid a warning
panic!(
"TODO wrap this expr in an Expr::Store: {:?}",
mono_expr
)
} else {
mono_expr
}));
}
Underscore => {
// We should always have exactly one default branch!
debug_assert!(opt_default_branch.is_none());
opt_default_branch = Some(arena.alloc(mono_expr));
}
Shadowed(_loc_ident) => {
panic!("TODO runtime error for shadowing in a pattern");
}
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
UnsupportedPattern(_region) => {
panic!("TODO runtime error for unsupported pattern");
}
AppliedTag(_, _, _) | StrLiteral(_) | RecordDestructure(_, _) => {
// The type checker should have converted these mismatches into RuntimeErrors already!
if cfg!(debug_assetions) {
panic!("A type mismatch in a pattern was not converted to a runtime error: {:?}", loc_pat);
} else {
unreachable!();
}
}
}
}
// If the default branch was never set, that means
// our canonical Expr didn't have one. An earlier
// step in the compilation process should have
// ruled this out!
debug_assert!(opt_default_branch.is_some());
let default_branch = opt_default_branch.unwrap();
Expr::Switch {
cond: arena.alloc(cond),
branches: jumpable_branches.into_bump_slice(),
default_branch,
ret_var: expr_var,
cond_var,
}
} else {
// /// More than two conditional branches, e.g. a 3-way when-expression
// Expr::Branches {
// /// The left-hand side of the conditional. We compile this to LLVM once,
// /// then reuse it to test against each different compiled cond_rhs value.
// cond_lhs: &'a Expr<'a>,
// /// ( cond_rhs, pass, fail )
// branches: &'a [(Expr<'a>, Expr<'a>, Expr<'a>)],
// ret_var: Variable,
// },
panic!("TODO support when-expressions of 3+ branches whose conditions aren't numbers.");
}
}
} }
} }

View file

@ -769,7 +769,7 @@ fn loc_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>> {
loc!(ident_pattern()), loc!(ident_pattern()),
loc!(record_destructure(min_indent)), loc!(record_destructure(min_indent)),
loc!(string_pattern()), loc!(string_pattern()),
loc!(int_pattern()) loc!(number_pattern())
) )
} }
@ -781,7 +781,7 @@ fn loc_parenthetical_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pat
) )
} }
fn int_pattern<'a>() -> impl Parser<'a, Pattern<'a>> { fn number_pattern<'a>() -> impl Parser<'a, Pattern<'a>> {
map_with_arena!(number_literal(), |arena, expr| { map_with_arena!(number_literal(), |arena, expr| {
expr_to_pattern(arena, &expr).unwrap() expr_to_pattern(arena, &expr).unwrap()
}) })
@ -1016,49 +1016,61 @@ fn unary_negate_function_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Exp
then( then(
// Spaces, then '-', then *not* more spaces. // Spaces, then '-', then *not* more spaces.
not_followed_by( not_followed_by(
and!(space1(min_indent), loc!(char('-'))), and!(
space1(min_indent),
either!(
// Try to parse a number literal *before* trying to parse unary negate,
// because otherwise (foo -1) will parse as (foo (Num.neg 1))
loc!(number_literal()),
loc!(char('-'))
)
),
one_of!(char(' '), char('#'), char('\n')), one_of!(char(' '), char('#'), char('\n')),
), ),
move |arena, state, (spaces, loc_minus_char)| { move |arena, state, (spaces, num_or_minus_char)| {
let region = loc_minus_char.region; match num_or_minus_char {
let loc_op = Located { Either::First(loc_num_literal) => Ok((loc_num_literal, state)),
region, Either::Second(Located { region, .. }) => {
value: UnaryOp::Negate, let loc_op = Located {
}; region,
value: UnaryOp::Negate,
};
// Continue parsing the function arg as normal. // Continue parsing the function arg as normal.
let (loc_expr, state) = loc_function_arg(min_indent).parse(arena, state)?; let (loc_expr, state) = loc_function_arg(min_indent).parse(arena, state)?;
let region = Region { let region = Region {
start_col: loc_op.region.start_col, start_col: loc_op.region.start_col,
start_line: loc_op.region.start_line, start_line: loc_op.region.start_line,
end_col: loc_expr.region.end_col, end_col: loc_expr.region.end_col,
end_line: loc_expr.region.end_line, end_line: loc_expr.region.end_line,
}; };
let value = Expr::UnaryOp(arena.alloc(loc_expr), loc_op); let value = Expr::UnaryOp(arena.alloc(loc_expr), loc_op);
let loc_expr = Located { let loc_expr = Located {
// Start from where the unary op started, // Start from where the unary op started,
// and end where its argument expr ended. // and end where its argument expr ended.
// This is relevant in case (for example) // This is relevant in case (for example)
// we have an expression involving parens, // we have an expression involving parens,
// for example `-(foo bar)` // for example `-(foo bar)`
region, region,
value, value,
}; };
// spaces can be empy if it's all space characters (no newlines or comments). // spaces can be empy if it's all space characters (no newlines or comments).
let value = if spaces.is_empty() { let value = if spaces.is_empty() {
loc_expr.value loc_expr.value
} else { } else {
Expr::SpaceBefore(arena.alloc(loc_expr.value), spaces) Expr::SpaceBefore(arena.alloc(loc_expr.value), spaces)
}; };
Ok(( Ok((
Located { Located {
region: loc_expr.region, region: loc_expr.region,
value, value,
}, },
state, state,
)) ))
}
}
}, },
) )
} }

View file

@ -2,6 +2,7 @@ use crate::can::ident::{Lowercase, ModuleName, Uppercase};
use crate::can::symbol::Symbol; use crate::can::symbol::Symbol;
use crate::collections::{ImMap, ImSet, MutSet, SendMap}; use crate::collections::{ImMap, ImSet, MutSet, SendMap};
use crate::ena::unify::{InPlace, UnificationTable, UnifyKey}; use crate::ena::unify::{InPlace, UnificationTable, UnifyKey};
use crate::types;
use crate::types::{name_type_var, ErrorType, Problem, RecordFieldLabel, TypeExt}; use crate::types::{name_type_var, ErrorType, Problem, RecordFieldLabel, TypeExt};
use crate::uniqueness::boolean_algebra; use crate::uniqueness::boolean_algebra;
use std::fmt; use std::fmt;
@ -427,6 +428,18 @@ pub enum Content {
Error, Error,
} }
impl Content {
#[inline(always)]
pub fn is_number(&self) -> bool {
match &self {
Content::Structure(FlatType::Apply {
module_name, name, ..
}) => module_name.as_str() == types::MOD_NUM && name.as_str() == types::TYPE_NUM,
_ => false,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum FlatType { pub enum FlatType {
Apply { Apply {

View file

@ -15,12 +15,13 @@ mod test_crane {
use bumpalo::Bump; use bumpalo::Bump;
use cranelift::prelude::*; use cranelift::prelude::*;
use cranelift_codegen::isa; use cranelift_codegen::isa;
use cranelift_codegen::settings::{self}; use cranelift_codegen::settings;
use cranelift_codegen::verifier::verify_function;
use cranelift_module::{default_libcall_names, Linkage, Module}; use cranelift_module::{default_libcall_names, Linkage, Module};
use cranelift_simplejit::{SimpleJITBackend, SimpleJITBuilder}; use cranelift_simplejit::{SimpleJITBackend, SimpleJITBuilder};
use roc::collections::{ImMap, MutMap}; use roc::collections::{ImMap, MutMap};
use roc::crane::build::{build_expr, declare_proc, define_proc_body, Env, ScopeEntry}; use roc::crane::build::{build_expr, declare_proc, define_proc_body, Env, ScopeEntry};
use roc::crane::convert::content_to_crane_type; use roc::crane::convert::type_from_content;
use roc::infer::infer_expr; use roc::infer::infer_expr;
use roc::mono::expr::Expr; use roc::mono::expr::Expr;
use roc::subs::Subs; use roc::subs::Subs;
@ -49,7 +50,7 @@ mod test_crane {
); );
} }
Ok(isa_builder) => { Ok(isa_builder) => {
let isa = isa_builder.finish(shared_flags); let isa = isa_builder.finish(shared_flags.clone());
isa.frontend_config() isa.frontend_config()
} }
@ -58,8 +59,7 @@ mod test_crane {
let main_fn_name = "$Test.main"; let main_fn_name = "$Test.main";
// Compute main_fn_ret_type before moving subs to Env // Compute main_fn_ret_type before moving subs to Env
let main_ret_type = content_to_crane_type(&content, &mut subs, cfg) let main_ret_type = type_from_content(&content, &mut subs, cfg);
.expect("Unable to infer type for test expr");
// Compile and add all the Procs before adding main // Compile and add all the Procs before adding main
let mut procs = MutMap::default(); let mut procs = MutMap::default();
@ -98,6 +98,14 @@ mod test_crane {
proc, proc,
&procs, &procs,
); );
// Verify the function we just defined
if let Err(errors) = verify_function(&ctx.func, &shared_flags) {
// NOTE: We don't include proc here because it's already
// been moved. If you need to know which proc failed, go back
// and add some logging.
panic!("Errors defining proc: {}", errors);
}
} }
// Add main itself // Add main itself
@ -114,16 +122,19 @@ mod test_crane {
{ {
let mut builder: FunctionBuilder = let mut builder: FunctionBuilder =
FunctionBuilder::new(&mut ctx.func, &mut func_ctx); FunctionBuilder::new(&mut ctx.func, &mut func_ctx);
let ebb = builder.create_ebb(); let block = builder.create_ebb();
builder.switch_to_block(ebb); builder.switch_to_block(block);
// TODO try deleting this line and seeing if everything still works. // TODO try deleting this line and seeing if everything still works.
builder.append_ebb_params_for_function_params(ebb); builder.append_ebb_params_for_function_params(block);
let main_body = let main_body =
build_expr(&env, &scope, &mut module, &mut builder, &mono_expr, &procs); build_expr(&env, &scope, &mut module, &mut builder, &mono_expr, &procs);
builder.ins().return_(&[main_body]); builder.ins().return_(&[main_body]);
// 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.seal_all_blocks(); builder.seal_all_blocks();
builder.finalize(); builder.finalize();
} }
@ -134,6 +145,11 @@ mod test_crane {
// Perform linking // Perform linking
module.finalize_definitions(); module.finalize_definitions();
// Verify the main function
if let Err(errors) = verify_function(&ctx.func, &shared_flags) {
panic!("Errors defining {} - {}", main_fn_name, errors);
}
let main_ptr = module.get_finalized_function(main_fn); let main_ptr = module.get_finalized_function(main_fn);
let run_main = unsafe { mem::transmute::<_, fn() -> $ty>(main_ptr) }; let run_main = unsafe { mem::transmute::<_, fn() -> $ty>(main_ptr) };
@ -151,48 +167,93 @@ 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 gen_when_take_first_branch() {
// assert_evals_to!( // assert_evals_to!(
// indoc!( // indoc!(
// r#" // r#"
// when 1 is // when 1 is
// 1 -> 12 // 1 -> 12
// _ -> 34 // _ -> 34
// "# // "#
// ), // ),
// 12, // 12,
// i64 // i64
// ); // );
// } // }
// #[test] // #[test]
// fn gen_when_take_second_branch() { // fn gen_when_take_second_branch() {
// assert_evals_to!( // assert_evals_to!(
// indoc!( // indoc!(
// r#" // r#"
// when 2 is // when 2 is
// 1 -> 63 // 1 -> 63
// _ -> 48 // _ -> 48
// "# // "#
// ), // ),
// 48, // 48,
// i64 // i64
// ); // );
// } // }
// #[test]
// fn gen_when_one_branch() { #[test]
// assert_evals_to!( fn gen_when_one_branch() {
// indoc!( assert_evals_to!(
// r#" indoc!(
// when 3.14 is r#"
// _ -> 23 when 3.14 is
// "# _ -> 23
// ), "#
// 23, ),
// i64 23,
// ); i64
// } );
}
#[test]
fn gen_large_when_int() {
assert_evals_to!(
indoc!(
r#"
foo = \num ->
when num is
0 -> 200
-3 -> 111
3 -> 789
1 -> 123
2 -> 456
_ -> 1000
foo -3
"#
),
111,
i64
);
}
#[test]
fn gen_large_when_float() {
assert_evals_to!(
indoc!(
r#"
foo = \num ->
when num is
0.5 -> 200.1
-3.6 -> 111.2
3.6 -> 789.5
1.7 -> 123.3
2.8 -> 456.4
_ -> 1000.6
foo -3.6
"#
),
111.2,
f64
);
}
#[test] #[test]
fn gen_basic_def() { fn gen_basic_def() {
@ -328,23 +389,24 @@ mod test_crane {
); );
} }
// #[test] #[test]
// fn gen_when_fn() { fn gen_when_fn() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// limitedNegate = \num -> limitedNegate = \num ->
// when num is when num is
// 1 -> -1 1 -> -1
// _ -> num -1 -> 1
_ -> num
// limitedNegate 1 limitedNegate 1
// "# "#
// ), ),
// -1, -1,
// i64 i64
// ); );
// } }
#[test] #[test]
fn apply_unnamed_fn() { fn apply_unnamed_fn() {