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 cranelift::frontend::Switch;
use cranelift::prelude::{
AbiParam, ExternalName, FunctionBuilder, FunctionBuilderContext, MemFlags,
};
@ -11,10 +13,10 @@ use cranelift_module::{Backend, FuncId, Linkage, Module};
use inlinable_string::InlinableString;
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::layout::Layout;
use crate::subs::Subs;
use crate::subs::{Content, FlatType, Subs, Variable};
type Scope = ImMap<InlinableString, ScopeEntry>;
@ -64,8 +66,23 @@ pub fn build_expr<'a, B: Backend>(
// build_cond(env, scope, cond, procs)
// }
Branches { .. } => {
panic!("TODO build_branches(env, scope, cond_lhs, branches, procs)");
Switch {
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) => {
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 content = subs.get_without_compacting(*var).content;
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 slot = builder.create_stack_slot(StackSlotData::new(
@ -110,7 +127,7 @@ pub fn build_expr<'a, B: Backend>(
} else if name == "Bool.and" {
panic!("TODO create a branch for &&");
} 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() {
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) => {
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() {
arg_vals.push(build_expr(env, scope, module, builder, arg, procs));
@ -209,7 +226,7 @@ pub fn build_expr<'a, B: Backend>(
// 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| {
// let ret_type = type_from_content(&content, subs, context).unwrap_or_else(|err| {
// panic!(
// "Error converting cond branch ret_type content {:?} to basic type: {:?}",
// cond.pass, err
@ -219,82 +236,14 @@ pub fn build_expr<'a, B: Backend>(
// 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");
// match (lhs_layout, rhs_layout) {
// // 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);
// 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 context = env.context;
// let lhs = build_expr(env, scope, cond_lhs, procs);
// let mut branch_iter = branches.into_iter();
// 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
// )
// });
// for (cond_rhs, cond_pass, cond_else) in branches {
// let rhs = build_expr(env, scope, cond_rhs, procs);
// let pass = build_expr(env, scope, cond_pass, procs);
// let fail = build_expr(env, scope, cond_else, procs);
// let cond = Cond {
// lhs,
// rhs,
// pass,
// fail,
// ret_type,
// };
// build_cond(env, scope, cond, procs)
// }
// }
// 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;
// let else_ebb = builder.create_ebb();
// builder.switch_to_block(else_ebb);
// // build branch
// let then_bb = context.append_basic_block("then");
@ -328,8 +277,150 @@ pub fn build_expr<'a, B: Backend>(
// ]);
// 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>(
env: &Env<'a>,
module: &mut Module<B>,
@ -339,14 +430,8 @@ pub fn declare_proc<'a, B: Backend>(
let args = proc.args;
let subs = &env.subs;
let cfg = env.cfg;
let ret_content = subs.get_without_compacting(proc.ret_var).content;
// TODO this content_to_crane_type is duplicated when building this Proc
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
)
});
// TODO this rtype_from_var is duplicated when building this Proc
let ret_type = type_from_var(proc.ret_var, subs, env.cfg);
// Create a signature for the function
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 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.append_ebb_params_for_function_params(ebb);
builder.switch_to_block(block);
builder.append_ebb_params_for_function_params(block);
// 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;
// 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)
.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);
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.finalize();
}

View file

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

View file

@ -64,9 +64,23 @@ pub enum Expr<'a> {
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: &'a Expr<'a>,
/// ( cond_rhs, pass, fail )
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,
},
Tag {
@ -193,9 +207,34 @@ fn from_can<'a>(
loc_cond,
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(
env,
loc_pattern.value,
loc_cond.value,
cond_var,
procs,
&mut stored,
);
let ret = from_can(env, loc_branch.value, procs, None);
Expr::Store(stored.into_bump_slice(), arena.alloc(ret))
}
2 => {
// A when-expression with exactly 2 branches compiles to a Cond.
let arena = env.arena;
let mut iter = branches.into_iter();
let (loc_pat1, loc_then) = iter.next().unwrap();
@ -216,7 +255,8 @@ fn from_can<'a>(
ret_var: expr_var,
}
}
(FloatLiteral(float), FloatLiteral(_)) | (FloatLiteral(float), Underscore) => {
(FloatLiteral(float), FloatLiteral(_))
| (FloatLiteral(float), Underscore) => {
let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None));
let cond_rhs = arena.alloc(Expr::Float(*float));
let pass = arena.alloc(from_can(env, loc_then.value, procs, None));
@ -234,25 +274,92 @@ fn from_can<'a>(
panic!("TODO handle more conds");
}
}
} else if branches.len() == 1 {
// A when-expression with exactly 1 branch is essentially a LetNonRec.
// As such, we can compile it direcly to a Store.
}
_ => {
// This is a when-expression with 3+ branches.
let arena = env.arena;
let mut stored = Vec::with_capacity_in(1, arena);
let (loc_pattern, loc_branch) = branches.into_iter().next().unwrap();
let cond = from_can(env, loc_cond.value, procs, None);
let content = env.subs.get_without_compacting(cond_var).content;
store_pattern(
env,
loc_pattern.value,
loc_cond.value,
// If the condition is an Int or Float, we can potentially use
// a Switch for more efficiency.
if content.is_number() {
// These are integer literals or underscore patterns,
// so they're eligible for user in a jump table.
let mut jumpable_branches = Vec::with_capacity_in(branches.len(), arena);
let mut opt_default_branch = None;
for (loc_pat, loc_expr) in branches {
let mono_expr = from_can(env, loc_expr.value, procs, None);
match &loc_pat.value {
IntLiteral(int) => {
// Switch only compares the condition to the
// alternatives based on their bit patterns,
// so casting from i64 to u64 makes no difference here.
jumpable_branches.push((*int as u64, mono_expr));
}
FloatLiteral(float) => {
// Switch only compares the condition to the
// alternatives based on their bit patterns,
// so casting from f64 to u64 makes no difference here.
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,
procs,
&mut stored,
);
let ret = from_can(env, loc_branch.value, procs, None);
Expr::Store(stored.into_bump_slice(), arena.alloc(ret))
}
} else {
// /// More than two conditional branches, e.g. a 3-way when-expression
// Expr::Branches {
@ -263,7 +370,9 @@ fn from_can<'a>(
// branches: &'a [(Expr<'a>, Expr<'a>, Expr<'a>)],
// ret_var: Variable,
// },
panic!("TODO support when-expressions of more than 2 branches.");
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!(record_destructure(min_indent)),
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| {
expr_to_pattern(arena, &expr).unwrap()
})
@ -1016,11 +1016,21 @@ fn unary_negate_function_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Exp
then(
// Spaces, then '-', then *not* more spaces.
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')),
),
move |arena, state, (spaces, loc_minus_char)| {
let region = loc_minus_char.region;
move |arena, state, (spaces, num_or_minus_char)| {
match num_or_minus_char {
Either::First(loc_num_literal) => Ok((loc_num_literal, state)),
Either::Second(Located { region, .. }) => {
let loc_op = Located {
region,
value: UnaryOp::Negate,
@ -1059,6 +1069,8 @@ fn unary_negate_function_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Exp
},
state,
))
}
}
},
)
}

View file

@ -2,6 +2,7 @@ use crate::can::ident::{Lowercase, ModuleName, Uppercase};
use crate::can::symbol::Symbol;
use crate::collections::{ImMap, ImSet, MutSet, SendMap};
use crate::ena::unify::{InPlace, UnificationTable, UnifyKey};
use crate::types;
use crate::types::{name_type_var, ErrorType, Problem, RecordFieldLabel, TypeExt};
use crate::uniqueness::boolean_algebra;
use std::fmt;
@ -427,6 +428,18 @@ pub enum Content {
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)]
pub enum FlatType {
Apply {

View file

@ -15,12 +15,13 @@ mod test_crane {
use bumpalo::Bump;
use cranelift::prelude::*;
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_simplejit::{SimpleJITBackend, SimpleJITBuilder};
use roc::collections::{ImMap, MutMap};
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::mono::expr::Expr;
use roc::subs::Subs;
@ -49,7 +50,7 @@ mod test_crane {
);
}
Ok(isa_builder) => {
let isa = isa_builder.finish(shared_flags);
let isa = isa_builder.finish(shared_flags.clone());
isa.frontend_config()
}
@ -58,8 +59,7 @@ mod test_crane {
let main_fn_name = "$Test.main";
// Compute main_fn_ret_type before moving subs to Env
let main_ret_type = content_to_crane_type(&content, &mut subs, cfg)
.expect("Unable to infer type for test expr");
let main_ret_type = type_from_content(&content, &mut subs, cfg);
// Compile and add all the Procs before adding main
let mut procs = MutMap::default();
@ -98,6 +98,14 @@ mod test_crane {
proc,
&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
@ -114,16 +122,19 @@ mod test_crane {
{
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);
// 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 =
build_expr(&env, &scope, &mut module, &mut builder, &mono_expr, &procs);
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.finalize();
}
@ -134,6 +145,11 @@ mod test_crane {
// Perform linking
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 run_main = unsafe { mem::transmute::<_, fn() -> $ty>(main_ptr) };
@ -180,19 +196,64 @@ mod test_crane {
// i64
// );
// }
// #[test]
// fn gen_when_one_branch() {
// assert_evals_to!(
// indoc!(
// r#"
// when 3.14 is
// _ -> 23
// "#
// ),
// 23,
// i64
// );
// }
#[test]
fn gen_when_one_branch() {
assert_evals_to!(
indoc!(
r#"
when 3.14 is
_ -> 23
"#
),
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]
fn gen_basic_def() {
@ -328,23 +389,24 @@ mod test_crane {
);
}
// #[test]
// fn gen_when_fn() {
// assert_evals_to!(
// indoc!(
// r#"
// limitedNegate = \num ->
// when num is
// 1 -> -1
// _ -> num
#[test]
fn gen_when_fn() {
assert_evals_to!(
indoc!(
r#"
limitedNegate = \num ->
when num is
1 -> -1
-1 -> 1
_ -> num
// limitedNegate 1
// "#
// ),
// -1,
// i64
// );
// }
limitedNegate 1
"#
),
-1,
i64
);
}
#[test]
fn apply_unnamed_fn() {