mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 08:34:33 +00:00
Get Switch running on Crane
This commit is contained in:
parent
750a9f9ca3
commit
e49b608157
6 changed files with 568 additions and 281 deletions
|
@ -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 (¶m, (_, arg_name, var)) in builder.ebb_params(ebb).iter().zip(args) {
|
||||
for (¶m, (_, 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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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!(
|
||||
|
|
151
src/mono/expr.rs
151
src/mono/expr.rs
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
13
src/subs.rs
13
src/subs.rs
|
@ -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 {
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue