mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 16:21:11 +00:00
Introduce ll module
This commit is contained in:
parent
297c9f567a
commit
55e4ce1134
13 changed files with 1141 additions and 513 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -596,6 +596,7 @@ dependencies = [
|
|||
"indoc 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"inkwell 0.1.0 (git+https://github.com/TheDan64/inkwell?branch=llvm8-0)",
|
||||
"inlinable_string 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -21,6 +21,7 @@ inlinable_string = "0.1.0"
|
|||
# but after several hours of trying unsuccessfully to fix it, `branch` is it.)
|
||||
inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "llvm8-0" }
|
||||
futures = "0.3"
|
||||
lazy_static = "1.4"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1"
|
||||
|
|
|
@ -20,6 +20,18 @@ impl fmt::Debug for Symbol {
|
|||
}
|
||||
}
|
||||
|
||||
impl Into<InlinableString> for Symbol {
|
||||
fn into(self) -> InlinableString {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InlinableString> for Symbol {
|
||||
fn from(string: InlinableString) -> Self {
|
||||
Symbol(string)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Symbol {
|
||||
fn from(string: &str) -> Self {
|
||||
Symbol(string.into())
|
||||
|
|
471
src/gen/build.rs
Normal file
471
src/gen/build.rs
Normal file
|
@ -0,0 +1,471 @@
|
|||
use bumpalo::Bump;
|
||||
use inkwell::module::Linkage;
|
||||
use inkwell::types::BasicType;
|
||||
use inkwell::types::BasicTypeEnum;
|
||||
use inkwell::values::BasicValueEnum::{self, *};
|
||||
use inkwell::values::{FunctionValue, IntValue, PointerValue};
|
||||
use inkwell::{FloatPredicate, IntPredicate};
|
||||
|
||||
use crate::can;
|
||||
use crate::collections::{ImMap, MutMap};
|
||||
use crate::gen::convert::content_to_basic_type;
|
||||
use crate::gen::env::Env;
|
||||
use crate::ll::expr::{Expr, Procs};
|
||||
use crate::subs::Variable;
|
||||
use inlinable_string::InlinableString;
|
||||
|
||||
/// This is for Inkwell's FunctionValue::verify - we want to know the verification
|
||||
/// output in debug builds, but we don't want it to print to stdout in release builds!
|
||||
#[cfg(debug_assertions)]
|
||||
const PRINT_FN_VERIFICATION_OUTPUT: bool = true;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
const PRINT_FN_VERIFICATION_OUTPUT: bool = false;
|
||||
|
||||
type Scope<'ctx> = ImMap<InlinableString, (Variable, PointerValue<'ctx>)>;
|
||||
|
||||
pub fn build_can_expr<'ctx, 'env>(
|
||||
env: &Env<'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
can_expr: can::expr::Expr,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let arena = Bump::new();
|
||||
|
||||
let mut procs = MutMap::default();
|
||||
let expr = Expr::new(
|
||||
&arena,
|
||||
&env.subs,
|
||||
env.module,
|
||||
env.context,
|
||||
can_expr,
|
||||
&mut procs,
|
||||
);
|
||||
|
||||
build_expr(
|
||||
env,
|
||||
&ImMap::default(),
|
||||
parent,
|
||||
&expr,
|
||||
&mut MutMap::default(),
|
||||
)
|
||||
}
|
||||
|
||||
fn build_expr<'a, 'ctx, 'env>(
|
||||
env: &Env<'ctx, 'env>,
|
||||
scope: &Scope<'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
expr: &Expr<'a>,
|
||||
procs: &mut Procs<'a, 'ctx>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
use crate::ll::expr::Expr::*;
|
||||
|
||||
match expr {
|
||||
Int(num) => env.context.i64_type().const_int(*num as u64, false).into(),
|
||||
Float(num) => env.context.f64_type().const_float(*num).into(),
|
||||
Cond {
|
||||
cond_lhs,
|
||||
cond_rhs,
|
||||
pass,
|
||||
fail,
|
||||
ret_var,
|
||||
} => {
|
||||
let cond = Cond2 {
|
||||
cond_lhs,
|
||||
cond_rhs,
|
||||
pass,
|
||||
fail,
|
||||
ret_var: *ret_var,
|
||||
};
|
||||
|
||||
build_cond(env, scope, parent, cond, procs)
|
||||
}
|
||||
Branches { .. } => {
|
||||
panic!("TODO build_branches(env, scope, parent, cond_lhs, branches, procs)");
|
||||
}
|
||||
Store(ref stores, ref ret) => {
|
||||
let mut scope = im_rc::HashMap::clone(scope);
|
||||
let subs = &env.subs;
|
||||
let context = &env.context;
|
||||
|
||||
for (name, var, expr) in stores.iter() {
|
||||
let content = subs.get_without_compacting(*var).content;
|
||||
let val = build_expr(env, &scope, parent, &expr, procs);
|
||||
let expr_bt =
|
||||
content_to_basic_type(&content, subs, context).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Error converting symbol {:?} to basic type: {:?} - scope was: {:?}",
|
||||
name, err, scope
|
||||
)
|
||||
});
|
||||
let alloca = create_entry_block_alloca(env, parent, expr_bt, &name);
|
||||
|
||||
env.builder.build_store(alloca, val);
|
||||
|
||||
// Make a new scope which includes the binding we just encountered.
|
||||
// This should be done *after* compiling the bound expr, since any
|
||||
// recursive (in the LetRec sense) bindings should already have
|
||||
// been extracted as procedures. Nothing in here should need to
|
||||
// access itself!
|
||||
scope = im_rc::HashMap::clone(&scope);
|
||||
|
||||
scope.insert(name.clone(), (*var, alloca));
|
||||
}
|
||||
|
||||
build_expr(env, &scope, parent, ret, procs)
|
||||
}
|
||||
CallByName(ref name, ref args) => {
|
||||
// TODO try one of these alternative strategies:
|
||||
//
|
||||
// 1. use SIMD string comparison to compare these strings faster
|
||||
// 2. pre-register Bool.or using module.add_function, and see if LLVM inlines it
|
||||
if name == "Bool.or" {
|
||||
panic!("TODO create a phi node for ||");
|
||||
} else if name == "Bool.and" {
|
||||
panic!("TODO create a phi node for &&");
|
||||
} else {
|
||||
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity(args.len());
|
||||
|
||||
for arg in args.iter() {
|
||||
arg_vals.push(build_expr(env, scope, parent, arg, procs).into());
|
||||
}
|
||||
|
||||
let fn_val = env
|
||||
.module
|
||||
.get_function(name)
|
||||
.unwrap_or_else(|| panic!("Unrecognized function: {:?}", name));
|
||||
|
||||
let call = env.builder.build_call(fn_val, arg_vals.as_slice(), "tmp");
|
||||
|
||||
call.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap_or_else(|| panic!("LLVM error: Invalid call by name."))
|
||||
}
|
||||
}
|
||||
CallByPointer(ref _ptr, ref args) => {
|
||||
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity(args.len());
|
||||
|
||||
for arg in args.iter() {
|
||||
arg_vals.push(build_expr(env, scope, parent, arg, procs).into());
|
||||
}
|
||||
|
||||
panic!("TODO do a load(ptr) to get back the pointer, then pass *that* in here!");
|
||||
|
||||
// let call = match build_expr(env, scope, parent, expr, procs) {
|
||||
// BasicValueEnum::PointerValue(ptr) => {
|
||||
// env.builder.build_call(ptr, arg_vals.as_slice(), "tmp")
|
||||
// }
|
||||
// non_ptr => {
|
||||
// panic!(
|
||||
// "Tried to call by pointer, but encountered a non-pointer: {:?}",
|
||||
// non_ptr
|
||||
// );
|
||||
// }
|
||||
// };
|
||||
|
||||
// call.try_as_basic_value()
|
||||
// .left()
|
||||
// .unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."))
|
||||
}
|
||||
|
||||
Load(name) => match scope.get(name) {
|
||||
Some((_, ptr)) => env.builder.build_load(*ptr, name),
|
||||
None => panic!("Could not find a var for {:?} in scope {:?}", name, scope),
|
||||
},
|
||||
_ => {
|
||||
panic!("I don't yet know how to build {:?}", expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Cond2<'a> {
|
||||
cond_lhs: &'a Expr<'a>,
|
||||
cond_rhs: &'a Expr<'a>,
|
||||
pass: &'a Expr<'a>,
|
||||
fail: &'a Expr<'a>,
|
||||
ret_var: Variable,
|
||||
}
|
||||
|
||||
fn build_cond<'a, 'ctx, 'env>(
|
||||
env: &Env<'ctx, 'env>,
|
||||
scope: &Scope<'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
cond: Cond2<'a>,
|
||||
procs: &mut Procs<'a, '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_basic_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, parent, cond.cond_lhs, procs);
|
||||
let rhs = build_expr(env, scope, parent, 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, parent, 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, parent, 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: &mut Procs<'a, 'ctx>,
|
||||
// ) -> BasicValueEnum<'ctx> {
|
||||
// let builder = env.builder;
|
||||
// let context = env.context;
|
||||
// let lhs = build_expr(env, scope, parent, 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_basic_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, parent, cond_rhs, procs);
|
||||
// let pass = build_expr(env, scope, parent, cond_pass, procs);
|
||||
// let fail = build_expr(env, scope, parent, cond_else, procs);
|
||||
|
||||
// let cond = Cond {
|
||||
// lhs,
|
||||
// rhs,
|
||||
// pass,
|
||||
// fail,
|
||||
// ret_type,
|
||||
// };
|
||||
|
||||
// build_cond(env, scope, parent, cond, procs)
|
||||
// }
|
||||
// }
|
||||
|
||||
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: &mut Procs<'a, 'ctx>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let builder = env.builder;
|
||||
let context = env.context;
|
||||
|
||||
// build branch
|
||||
let then_bb = context.append_basic_block(parent, "then");
|
||||
let else_bb = context.append_basic_block(parent, "else");
|
||||
let cont_bb = context.append_basic_block(parent, "branchcont");
|
||||
|
||||
builder.build_conditional_branch(comparison, &then_bb, &else_bb);
|
||||
|
||||
// build then block
|
||||
builder.position_at_end(&then_bb);
|
||||
let then_val = build_expr(env, scope, parent, pass, procs);
|
||||
builder.build_unconditional_branch(&cont_bb);
|
||||
|
||||
let then_bb = builder.get_insert_block().unwrap();
|
||||
|
||||
// build else block
|
||||
builder.position_at_end(&else_bb);
|
||||
let else_val = build_expr(env, scope, parent, fail, procs);
|
||||
builder.build_unconditional_branch(&cont_bb);
|
||||
|
||||
let else_bb = builder.get_insert_block().unwrap();
|
||||
|
||||
// emit merge block
|
||||
builder.position_at_end(&cont_bb);
|
||||
|
||||
let phi = builder.build_phi(ret_type, "branch");
|
||||
|
||||
phi.add_incoming(&[
|
||||
(&Into::<BasicValueEnum>::into(then_val), &then_bb),
|
||||
(&Into::<BasicValueEnum>::into(else_val), &else_bb),
|
||||
]);
|
||||
|
||||
phi.as_basic_value()
|
||||
}
|
||||
|
||||
/// TODO could this be added to Inkwell itself as a method on BasicValueEnum?
|
||||
fn set_name(bv_enum: BasicValueEnum<'_>, name: &str) {
|
||||
match bv_enum {
|
||||
ArrayValue(val) => val.set_name(name),
|
||||
IntValue(val) => val.set_name(name),
|
||||
FloatValue(val) => val.set_name(name),
|
||||
PointerValue(val) => val.set_name(name),
|
||||
StructValue(val) => val.set_name(name),
|
||||
VectorValue(val) => val.set_name(name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_closure<'a, 'ctx, BT>(
|
||||
env: &Env<'ctx, '_>,
|
||||
name: InlinableString,
|
||||
arg_vars: Vec<Variable>,
|
||||
arg_names: &[InlinableString],
|
||||
ret_type: BT,
|
||||
body_expr: &Expr<'a>,
|
||||
scope: &Scope<'ctx>,
|
||||
procs: &mut Procs<'a, 'ctx>,
|
||||
linkage: Option<Linkage>,
|
||||
) -> FunctionValue<'ctx>
|
||||
where
|
||||
BT: BasicType<'ctx>,
|
||||
{
|
||||
// We need these to be separate, but they must have the same length!
|
||||
debug_assert!(arg_vars.len() == arg_names.len());
|
||||
|
||||
let subs = &env.subs;
|
||||
|
||||
// Register the function value in the module
|
||||
let mut arg_basic_types = Vec::with_capacity(arg_vars.len());
|
||||
|
||||
for var in arg_vars.iter() {
|
||||
let content = subs.get_without_compacting(*var).content;
|
||||
|
||||
arg_basic_types.push(
|
||||
content_to_basic_type(&content, &env.subs, env.context).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Error converting function arg content to basic type: {:?}",
|
||||
err
|
||||
)
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
let fn_type = ret_type.fn_type(arg_basic_types.as_slice(), false);
|
||||
let fn_val = env.module.add_function(&name, fn_type, linkage);
|
||||
|
||||
let entry = env.context.append_basic_block(fn_val, "entry");
|
||||
let builder = env.builder;
|
||||
|
||||
builder.position_at_end(&entry);
|
||||
|
||||
let mut scope = scope.clone();
|
||||
|
||||
// Add args to scope
|
||||
for (((arg_val, arg_name), arg_type), var) in fn_val
|
||||
.get_param_iter()
|
||||
.zip(arg_names)
|
||||
.zip(arg_basic_types)
|
||||
.zip(arg_vars.into_iter())
|
||||
{
|
||||
set_name(arg_val, arg_name);
|
||||
|
||||
let alloca = create_entry_block_alloca(env, fn_val, arg_type, arg_name);
|
||||
|
||||
builder.build_store(alloca, arg_val);
|
||||
|
||||
scope.insert(arg_name.clone(), (var, alloca));
|
||||
}
|
||||
|
||||
let body = build_expr(env, &scope, fn_val, body_expr, procs);
|
||||
|
||||
builder.build_return(Some(&body));
|
||||
|
||||
fn_val
|
||||
}
|
||||
|
||||
/// Creates a new stack allocation instruction in the entry block of the function.
|
||||
pub fn create_entry_block_alloca<'ctx>(
|
||||
env: &Env<'ctx, '_>,
|
||||
parent: FunctionValue<'_>,
|
||||
basic_type: BasicTypeEnum<'ctx>,
|
||||
name: &str,
|
||||
) -> PointerValue<'ctx> {
|
||||
let builder = env.context.create_builder();
|
||||
let entry = parent.get_first_basic_block().unwrap();
|
||||
|
||||
match entry.get_first_instruction() {
|
||||
Some(first_instr) => builder.position_before(&first_instr),
|
||||
None => builder.position_at_end(&entry),
|
||||
}
|
||||
|
||||
builder.build_alloca(basic_type, name)
|
||||
}
|
||||
|
||||
// fn build_proc() {
|
||||
// let (ref loc_body_expr, ret_var) = **body;
|
||||
// let subs = &env.subs;
|
||||
// let mut arg_types = Vec::new();
|
||||
// let mut arg_names = Vec::new();
|
||||
// let ret_content = subs.get_without_compacting(ret_var).content;
|
||||
// let ret_type =
|
||||
// content_to_basic_type(&ret_content, &env.subs, env.context).unwrap_or_else(|err| {
|
||||
// panic!(
|
||||
// "Error converting symbol {:?} to basic type: {:?} - scope was: {:?}",
|
||||
// symbol, err, scope
|
||||
// )
|
||||
// });
|
||||
|
||||
// for (var, loc_pat) in args {
|
||||
// let content = subs.get_without_compacting(*var).content;
|
||||
// let name = match &loc_pat.value {
|
||||
// Pattern::Identifier(ident) => ident.as_str().into(),
|
||||
// pat => {
|
||||
// panic!("TODO code gen function arg for pattern {:?}", pat);
|
||||
// }
|
||||
// };
|
||||
|
||||
// arg_types.push(content);
|
||||
// arg_names.push(name);
|
||||
// }
|
||||
|
||||
// let fn_val = build_closure(
|
||||
// env,
|
||||
// symbol.as_str().into(),
|
||||
// arg_types,
|
||||
// arg_names.as_slice(),
|
||||
// ret_type,
|
||||
// &loc_body_expr.value,
|
||||
// scope,
|
||||
// procs,
|
||||
// None,
|
||||
// );
|
||||
|
||||
// if fn_val.verify(PRINT_FN_VERIFICATION_OUTPUT) {
|
||||
// // TODO call pass_manager.run_on(&fn_val) to optimize it!
|
||||
|
||||
// // The closure evaluates to a pointer to the function.
|
||||
// fn_val
|
||||
// .as_global_value()
|
||||
// .as_pointer_value()
|
||||
// .as_basic_value_enum()
|
||||
// } else {
|
||||
// unsafe {
|
||||
// fn_val.delete();
|
||||
// }
|
||||
|
||||
// panic!("Invalid generated fn_val.")
|
||||
// }
|
||||
// }
|
|
@ -1,478 +0,0 @@
|
|||
use inkwell::basic_block::BasicBlock;
|
||||
use inkwell::module::Linkage;
|
||||
use inkwell::types::BasicType;
|
||||
use inkwell::types::BasicTypeEnum;
|
||||
use inkwell::values::BasicValueEnum::{self, *};
|
||||
use inkwell::values::{BasicValue, FunctionValue, IntValue, PointerValue};
|
||||
use inkwell::{FloatPredicate, IntPredicate};
|
||||
|
||||
use crate::can::expr::Expr;
|
||||
use crate::can::ident::Lowercase;
|
||||
use crate::can::pattern::Pattern::{self, *};
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::ImMap;
|
||||
use crate::collections::MutMap;
|
||||
use crate::gen::convert::content_to_basic_type;
|
||||
use crate::gen::env::Env;
|
||||
use crate::subs::{Content, FlatType, Subs};
|
||||
|
||||
/// This is for Inkwell's FunctionValue::verify - we want to know the verification
|
||||
/// output in debug builds, but we don't want it to print to stdout in release builds!
|
||||
#[cfg(debug_assertions)]
|
||||
const PRINT_FN_VERIFICATION_OUTPUT: bool = true;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
const PRINT_FN_VERIFICATION_OUTPUT: bool = false;
|
||||
|
||||
type Procs<'ctx> = MutMap<Symbol, (Content, FunctionValue<'ctx>)>;
|
||||
|
||||
type Scope<'ctx> = ImMap<Symbol, (Content, PointerValue<'ctx>)>;
|
||||
|
||||
pub fn compile_standalone_expr<'ctx, 'env>(
|
||||
env: &Env<'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
expr: &Expr,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
compile_expr(env, &ImMap::default(), parent, expr, &mut MutMap::default())
|
||||
}
|
||||
|
||||
fn compile_expr<'ctx, 'env>(
|
||||
env: &Env<'ctx, 'env>,
|
||||
scope: &Scope<'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
expr: &Expr,
|
||||
procs: &mut Procs,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
use crate::can::expr::Expr::*;
|
||||
|
||||
match *expr {
|
||||
Int(_, num) => env.context.i64_type().const_int(num as u64, false).into(),
|
||||
Float(_, num) => env.context.f64_type().const_float(num).into(),
|
||||
When {
|
||||
ref loc_cond,
|
||||
ref branches,
|
||||
..
|
||||
} => {
|
||||
if branches.len() < 2 {
|
||||
panic!("TODO support when-expressions of fewer than 2 branches.");
|
||||
}
|
||||
if branches.len() == 2 {
|
||||
let mut iter = branches.iter();
|
||||
|
||||
let (pattern, branch_expr) = iter.next().unwrap();
|
||||
let (_, else_expr) = iter.next().unwrap();
|
||||
|
||||
compile_when_branch(
|
||||
env,
|
||||
scope,
|
||||
parent,
|
||||
&loc_cond.value,
|
||||
pattern.value.clone(),
|
||||
&branch_expr.value,
|
||||
&else_expr.value,
|
||||
procs,
|
||||
)
|
||||
} else {
|
||||
panic!("TODO support when-expressions of more than 2 branches.");
|
||||
}
|
||||
}
|
||||
LetNonRec(ref def, ref loc_ret, _) => {
|
||||
match &def.loc_pattern.value {
|
||||
Pattern::Identifier(symbol) => {
|
||||
let expr = &def.loc_expr.value;
|
||||
let subs = &env.subs;
|
||||
let context = &env.context;
|
||||
let content = content_from_expr(scope, subs, expr);
|
||||
let val = compile_expr(env, &scope, parent, &expr, procs);
|
||||
let expr_bt = content_to_basic_type(&content, subs, context).unwrap_or_else(|err| panic!("Error converting symbol {:?} to basic type: {:?} - scope was: {:?}", symbol, err, scope));
|
||||
let alloca = create_entry_block_alloca(env, parent, expr_bt, symbol.as_str());
|
||||
|
||||
env.builder.build_store(alloca, val);
|
||||
|
||||
// Make a new scope which includes the binding we just encountered.
|
||||
// This should be done *after* compiling the bound expr, since this is a
|
||||
// LetNonRec rather than a LetRec. It shouldn't need to access itself!
|
||||
let mut scope = scope.clone();
|
||||
|
||||
scope.insert(symbol.clone(), (content.clone(), alloca));
|
||||
|
||||
compile_expr(env, &scope, parent, &loc_ret.value, procs)
|
||||
}
|
||||
pat => {
|
||||
panic!("TODO code gen Def pattern {:?}", pat);
|
||||
}
|
||||
}
|
||||
}
|
||||
Closure(_, ref symbol, ref _recursive, ref args, ref body) => {
|
||||
let (ref loc_body_expr, ret_var) = **body;
|
||||
let subs = &env.subs;
|
||||
let mut arg_types = Vec::new();
|
||||
let mut arg_names = Vec::new();
|
||||
let ret_content = subs.get_without_compacting(ret_var).content;
|
||||
let ret_type = content_to_basic_type(&ret_content, &env.subs, env.context)
|
||||
.unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Error converting symbol {:?} to basic type: {:?} - scope was: {:?}",
|
||||
symbol, err, scope
|
||||
)
|
||||
});
|
||||
|
||||
for (var, loc_pat) in args {
|
||||
let content = subs.get_without_compacting(*var).content;
|
||||
let name = match &loc_pat.value {
|
||||
Pattern::Identifier(ident) => ident.as_str().into(),
|
||||
pat => {
|
||||
panic!("TODO code gen function arg for pattern {:?}", pat);
|
||||
}
|
||||
};
|
||||
|
||||
arg_types.push(content);
|
||||
arg_names.push(name);
|
||||
}
|
||||
|
||||
let fn_val = compile_closure(
|
||||
env,
|
||||
symbol.as_str().into(),
|
||||
arg_types,
|
||||
arg_names.as_slice(),
|
||||
ret_type,
|
||||
&loc_body_expr.value,
|
||||
scope,
|
||||
procs,
|
||||
None,
|
||||
);
|
||||
|
||||
if fn_val.verify(PRINT_FN_VERIFICATION_OUTPUT) {
|
||||
// TODO call pass_manager.run_on(&fn_val) to optimize it!
|
||||
|
||||
// The closure evaluates to a pointer to the function.
|
||||
fn_val
|
||||
.as_global_value()
|
||||
.as_pointer_value()
|
||||
.as_basic_value_enum()
|
||||
} else {
|
||||
unsafe {
|
||||
fn_val.delete();
|
||||
}
|
||||
|
||||
panic!("Invalid generated fn_val.")
|
||||
}
|
||||
}
|
||||
Call(ref boxed, ref loc_args, _) => {
|
||||
let (_, ref loc_expr, _) = **boxed;
|
||||
let mut arg_vars: Vec<BasicValueEnum> = Vec::with_capacity(loc_args.len());
|
||||
|
||||
for (_var, loc_arg) in loc_args.iter() {
|
||||
let arg = compile_expr(env, scope, parent, &loc_arg.value, procs);
|
||||
|
||||
arg_vars.push(arg.into());
|
||||
}
|
||||
|
||||
let call = match &loc_expr.value {
|
||||
// TODO fix and then re-enable this. The problem is that it fails on
|
||||
// symbol lookup, because the closure's stored Symbol is always the
|
||||
// auto-generated one like "Test.blah$1" whereas the lookup is always
|
||||
// something like "Test.blah$identity" - so if we want to call by
|
||||
// name directly, we need to detect the closure's name *during code gen*
|
||||
// (while processing defs) and insert it into scope accordingly.
|
||||
// Afterwards, we should remove that Symbol from Expr.
|
||||
//
|
||||
// Var {
|
||||
// resolved_symbol, ..
|
||||
// } => {
|
||||
// // Call by name
|
||||
// let fn_val = env
|
||||
// .module
|
||||
// .get_function(resolved_symbol.as_str())
|
||||
// .unwrap_or_else(|| panic!("Unrecognized function: {:?}", resolved_symbol));
|
||||
|
||||
// env.builder.build_call(fn_val, arg_vars.as_slice(), "tmp")
|
||||
// }
|
||||
expr => {
|
||||
// Call by pointer - the closure was anonymous, e.g. ((\a -> a) 5)
|
||||
match compile_expr(env, scope, parent, expr, procs) {
|
||||
BasicValueEnum::PointerValue(ptr) => {
|
||||
env.builder.build_call(ptr, arg_vars.as_slice(), "tmp")
|
||||
}
|
||||
non_ptr => {
|
||||
panic!(
|
||||
"Tried to call by pointer, but encountered a non-pointer: {:?}",
|
||||
non_ptr
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
call.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap_or_else(|| panic!("Invalid call produced."))
|
||||
}
|
||||
|
||||
Var {
|
||||
ref resolved_symbol,
|
||||
..
|
||||
} => match scope.get(resolved_symbol) {
|
||||
Some((_, ptr)) => env.builder.build_load(*ptr, resolved_symbol.as_str()),
|
||||
None => panic!(
|
||||
"Could not find a var for {:?} in scope {:?}",
|
||||
resolved_symbol, scope
|
||||
),
|
||||
},
|
||||
_ => {
|
||||
panic!("I don't yet know how to compile {:?}", expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn content_from_expr(scope: &Scope<'_>, subs: &Subs, expr: &Expr) -> Content {
|
||||
use crate::can::expr::Expr::*;
|
||||
|
||||
match expr {
|
||||
Int(var, _)
|
||||
| Float(var, _)
|
||||
| When { expr_var: var, .. }
|
||||
| LetNonRec(_, _, var)
|
||||
| LetRec(_, _, var)
|
||||
| Closure(var, _, _, _, _) => subs.get_without_compacting(*var).content,
|
||||
Str(_) | BlockStr(_) => Content::Structure(FlatType::Apply {
|
||||
module_name: "Str".into(),
|
||||
name: "Str".into(),
|
||||
args: Vec::new(),
|
||||
}),
|
||||
Var {
|
||||
ref resolved_symbol,
|
||||
..
|
||||
} => {
|
||||
let (content, _) = scope.get(resolved_symbol).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Code gen problem: Couldn't find {:?} in scope {:?}",
|
||||
resolved_symbol, scope
|
||||
)
|
||||
});
|
||||
|
||||
content.clone()
|
||||
}
|
||||
other => panic!("TODO handle content_from_expr for {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_when_branch<'ctx, 'env>(
|
||||
env: &Env<'ctx, 'env>,
|
||||
scope: &Scope<'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
cond_expr: &Expr,
|
||||
pattern: Pattern,
|
||||
branch_expr: &Expr,
|
||||
else_expr: &Expr,
|
||||
procs: &mut Procs,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let builder = env.builder;
|
||||
let context = env.context;
|
||||
|
||||
match compile_expr(env, scope, parent, cond_expr, procs) {
|
||||
FloatValue(float_val) => match pattern {
|
||||
FloatLiteral(target_val) => {
|
||||
let comparison = builder.build_float_compare(
|
||||
FloatPredicate::OEQ,
|
||||
float_val,
|
||||
context.f64_type().const_float(target_val),
|
||||
"whencond",
|
||||
);
|
||||
|
||||
let (then_bb, else_bb, then_val, else_val) = two_way_branch(
|
||||
env,
|
||||
scope,
|
||||
parent,
|
||||
comparison,
|
||||
branch_expr,
|
||||
else_expr,
|
||||
procs,
|
||||
);
|
||||
let phi = builder.build_phi(context.f64_type(), "whenbranch");
|
||||
|
||||
phi.add_incoming(&[
|
||||
(&Into::<BasicValueEnum>::into(then_val), &then_bb),
|
||||
(&Into::<BasicValueEnum>::into(else_val), &else_bb),
|
||||
]);
|
||||
|
||||
phi.as_basic_value().into_float_value().into()
|
||||
}
|
||||
|
||||
_ => panic!("TODO support pattern matching on floats other than literals."),
|
||||
},
|
||||
|
||||
IntValue(int_val) => match pattern {
|
||||
IntLiteral(target_val) => {
|
||||
let comparison = builder.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
int_val,
|
||||
context.i64_type().const_int(target_val as u64, false),
|
||||
"whencond",
|
||||
);
|
||||
|
||||
let (then_bb, else_bb, then_val, else_val) = two_way_branch(
|
||||
env,
|
||||
scope,
|
||||
parent,
|
||||
comparison,
|
||||
branch_expr,
|
||||
else_expr,
|
||||
procs,
|
||||
);
|
||||
let phi = builder.build_phi(context.i64_type(), "whenbranch");
|
||||
|
||||
phi.add_incoming(&[
|
||||
(&Into::<BasicValueEnum>::into(then_val), &then_bb),
|
||||
(&Into::<BasicValueEnum>::into(else_val), &else_bb),
|
||||
]);
|
||||
|
||||
phi.as_basic_value().into_int_value().into()
|
||||
}
|
||||
_ => panic!("TODO support pattern matching on ints other than literals."),
|
||||
},
|
||||
_ => panic!(
|
||||
"TODO handle pattern matching on conditionals other than int and float literals."
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn two_way_branch<'ctx, 'env>(
|
||||
env: &Env<'ctx, 'env>,
|
||||
scope: &Scope<'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
comparison: IntValue<'ctx>,
|
||||
branch_expr: &Expr,
|
||||
else_expr: &Expr,
|
||||
procs: &mut Procs,
|
||||
) -> (
|
||||
BasicBlock,
|
||||
BasicBlock,
|
||||
BasicValueEnum<'ctx>,
|
||||
BasicValueEnum<'ctx>,
|
||||
) {
|
||||
let builder = env.builder;
|
||||
let context = env.context;
|
||||
|
||||
// build branch
|
||||
let then_bb = context.append_basic_block(parent, "then");
|
||||
let else_bb = context.append_basic_block(parent, "else");
|
||||
let cont_bb = context.append_basic_block(parent, "casecont");
|
||||
|
||||
builder.build_conditional_branch(comparison, &then_bb, &else_bb);
|
||||
|
||||
// build then block
|
||||
builder.position_at_end(&then_bb);
|
||||
let then_val = compile_expr(env, scope, parent, branch_expr, procs);
|
||||
builder.build_unconditional_branch(&cont_bb);
|
||||
|
||||
let then_bb = builder.get_insert_block().unwrap();
|
||||
|
||||
// build else block
|
||||
builder.position_at_end(&else_bb);
|
||||
let else_val = compile_expr(env, scope, parent, else_expr, procs);
|
||||
builder.build_unconditional_branch(&cont_bb);
|
||||
|
||||
let else_bb = builder.get_insert_block().unwrap();
|
||||
|
||||
// emit merge block
|
||||
builder.position_at_end(&cont_bb);
|
||||
|
||||
(then_bb, else_bb, then_val, else_val)
|
||||
}
|
||||
|
||||
/// TODO could this be added to Inkwell itself as a method on BasicValueEnum?
|
||||
fn set_name(bv_enum: BasicValueEnum<'_>, name: &str) {
|
||||
match bv_enum {
|
||||
ArrayValue(val) => val.set_name(name),
|
||||
IntValue(val) => val.set_name(name),
|
||||
FloatValue(val) => val.set_name(name),
|
||||
PointerValue(val) => val.set_name(name),
|
||||
StructValue(val) => val.set_name(name),
|
||||
VectorValue(val) => val.set_name(name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile_closure<'ctx, BT>(
|
||||
env: &Env<'ctx, '_>,
|
||||
name: Lowercase,
|
||||
arg_types: Vec<Content>,
|
||||
arg_names: &[Lowercase],
|
||||
ret_type: BT,
|
||||
body_expr: &Expr,
|
||||
scope: &Scope<'ctx>,
|
||||
procs: &mut Procs,
|
||||
linkage: Option<Linkage>,
|
||||
) -> FunctionValue<'ctx>
|
||||
where
|
||||
BT: BasicType<'ctx>,
|
||||
{
|
||||
// We need these to be separate, but they must have the same length!
|
||||
debug_assert!(arg_types.len() == arg_names.len());
|
||||
|
||||
// Register the function value in the module
|
||||
let mut arg_basic_types = Vec::with_capacity(arg_types.len());
|
||||
|
||||
for content in arg_types.iter() {
|
||||
arg_basic_types.push(
|
||||
content_to_basic_type(content, &env.subs, env.context).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Error converting function arg content to basic type: {:?}",
|
||||
err
|
||||
)
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
let fn_type = ret_type.fn_type(arg_basic_types.as_slice(), false);
|
||||
let fn_val = env.module.add_function(name.as_str(), fn_type, linkage);
|
||||
|
||||
let entry = env.context.append_basic_block(fn_val, "entry");
|
||||
let builder = env.builder;
|
||||
|
||||
builder.position_at_end(&entry);
|
||||
|
||||
let mut scope = scope.clone();
|
||||
|
||||
// Add args to scope
|
||||
for (((arg_val, arg_name), arg_type), content) in fn_val
|
||||
.get_param_iter()
|
||||
.zip(arg_names)
|
||||
.zip(arg_basic_types)
|
||||
.zip(arg_types.into_iter())
|
||||
{
|
||||
let arg_name = arg_name.as_str();
|
||||
|
||||
set_name(arg_val, arg_name);
|
||||
|
||||
let alloca = create_entry_block_alloca(env, fn_val, arg_type, arg_name);
|
||||
|
||||
builder.build_store(alloca, arg_val);
|
||||
|
||||
scope.insert(arg_name.into(), (content, alloca));
|
||||
}
|
||||
|
||||
let body = compile_expr(env, &scope, fn_val, body_expr, procs);
|
||||
|
||||
builder.build_return(Some(&body));
|
||||
|
||||
fn_val
|
||||
}
|
||||
|
||||
/// Creates a new stack allocation instruction in the entry block of the function.
|
||||
pub fn create_entry_block_alloca<'ctx>(
|
||||
env: &Env<'ctx, '_>,
|
||||
parent: FunctionValue<'_>,
|
||||
basic_type: BasicTypeEnum<'ctx>,
|
||||
name: &str,
|
||||
) -> PointerValue<'ctx> {
|
||||
let builder = env.context.create_builder();
|
||||
let entry = parent.get_first_basic_block().unwrap();
|
||||
|
||||
match entry.get_first_instruction() {
|
||||
Some(first_instr) => builder.position_before(&first_instr),
|
||||
None => builder.position_at_end(&entry),
|
||||
}
|
||||
|
||||
builder.build_alloca(basic_type, name)
|
||||
}
|
|
@ -6,6 +6,6 @@ use inkwell::module::Module;
|
|||
pub struct Env<'ctx, 'env> {
|
||||
pub context: &'ctx Context,
|
||||
pub builder: &'env Builder<'ctx>,
|
||||
pub module: &'env Module<'ctx>,
|
||||
pub module: &'ctx Module<'ctx>,
|
||||
pub subs: Subs,
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
pub mod compile;
|
||||
pub mod build;
|
||||
pub mod convert;
|
||||
pub mod env;
|
||||
|
|
89
src/gen/proc.rs
Normal file
89
src/gen/proc.rs
Normal file
|
@ -0,0 +1,89 @@
|
|||
use inkwell::basic_block::BasicBlock;
|
||||
use inkwell::module::Linkage;
|
||||
use inkwell::types::BasicType;
|
||||
use inkwell::types::BasicTypeEnum;
|
||||
use inkwell::values::BasicValueEnum::{self, *};
|
||||
use inkwell::values::{BasicValue, FunctionValue, IntValue, PointerValue};
|
||||
use inkwell::{FloatPredicate, IntPredicate};
|
||||
|
||||
use crate::can::expr::Expr;
|
||||
use crate::can::ident::Lowercase;
|
||||
use crate::can::pattern::Pattern::{self, *};
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::ImMap;
|
||||
use crate::collections::MutMap;
|
||||
use crate::gen::convert::content_to_basic_type;
|
||||
use crate::gen::env::Env;
|
||||
use crate::subs::{Content, FlatType, Subs};
|
||||
|
||||
type Procs<'ctx> = MutMap<Symbol, (Content, FunctionValue<'ctx>)>;
|
||||
|
||||
fn extract_procs(loc_expr: Located<Expr>, module: &Module<'ctx>, name: Option<Lowercase>, procs, &mut Procs<'ctx>) -> Located<Expr> {
|
||||
let mut procs = Vec::new();
|
||||
|
||||
match expr {
|
||||
LetNonRec(def, ret_expr, var) => {
|
||||
let loc_pattern = def.loc_pattern;
|
||||
let loc_expr = def.loc_expr;
|
||||
|
||||
// If we're defining a named closure, insert it into Procs and then
|
||||
// remove the Let. When code later goes to look it up, it'll be in Procs!
|
||||
//
|
||||
// Before:
|
||||
//
|
||||
// identity = \a -> a
|
||||
//
|
||||
// identity 5
|
||||
//
|
||||
// After: (`identity` is now in Procs)
|
||||
//
|
||||
// identity 5
|
||||
//
|
||||
let pattern = match loc_pattern.value {
|
||||
Identifier(name) => {
|
||||
match &loc_expr.value {
|
||||
Closure(_, _, _, _, _) => {
|
||||
// Extract Procs, but discard the resulting Expr::Var.
|
||||
// That Var looks up the pointer, which we won't use here!
|
||||
extract_procs(loc_expr, Some(name), procs);
|
||||
|
||||
// Discard this LetNonRec by replacing it with its ret_expr.
|
||||
return ret_expr;
|
||||
}
|
||||
_ => {
|
||||
// If this isn't a Closure, proceed as normal.
|
||||
Identifier(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
pat => pat
|
||||
}
|
||||
|
||||
// At this point, it's safe to assume we aren't assigning a Closure to a def.
|
||||
// Extract Procs from the def body and the ret expression, and return the result!
|
||||
let ret_expr = extract_procs(ret_expr, None, procs);
|
||||
let loc_expr = extract_procs(def.loc_expr, None, procs);
|
||||
let loc_pattern = Located { region: def.loc_pattern.region, value: pattern };
|
||||
let def = Def { loc_pattern, loc_expr, ..def };
|
||||
|
||||
LetNonRec(def, ret_expr, var)
|
||||
}
|
||||
|
||||
Closure(var, symbol, recursive, loc_args, boxed_ret) => {
|
||||
let (loc_ret, var) = boxed_ret;
|
||||
let name = match name {
|
||||
Some(name) => name.as_str(),
|
||||
None => {
|
||||
// Give the closure a name like "_0" or "_1".
|
||||
// We know procs.len() will be unique!
|
||||
format!("_{}", procs.len()).as_str();
|
||||
}
|
||||
};
|
||||
|
||||
let fn_val = module.add_function(name, fn_type, linkage);
|
||||
|
||||
panic!("push to procs");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ pub mod ena;
|
|||
pub mod fmt;
|
||||
pub mod gen;
|
||||
pub mod infer;
|
||||
pub mod ll;
|
||||
pub mod load;
|
||||
pub mod module;
|
||||
pub mod pretty_print_types;
|
||||
|
@ -37,3 +38,6 @@ pub mod unify;
|
|||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
// #[macro_use]
|
||||
// extern crate lazy_static;
|
||||
|
|
400
src/ll/expr.rs
Normal file
400
src/ll/expr.rs
Normal file
|
@ -0,0 +1,400 @@
|
|||
use crate::can;
|
||||
use crate::can::pattern::Pattern;
|
||||
use crate::collections::MutMap;
|
||||
use crate::gen::convert::content_to_basic_type;
|
||||
use crate::ll::layout::Layout;
|
||||
use crate::region::Located;
|
||||
use crate::subs::{Subs, Variable};
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use inkwell::context::Context;
|
||||
use inkwell::module::Module;
|
||||
use inkwell::types::{BasicType, BasicTypeEnum};
|
||||
use inkwell::values::FunctionValue;
|
||||
use inlinable_string::InlinableString;
|
||||
|
||||
pub type Procs<'a, 'ctx> = MutMap<InlinableString, (Option<Proc<'a>>, FunctionValue<'ctx>)>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Proc<'a> {
|
||||
args: &'a [(Layout<'a>, InlinableString)],
|
||||
body: Expr<'a>,
|
||||
closes_over: Layout<'a>,
|
||||
}
|
||||
|
||||
struct Env<'a, 'ctx> {
|
||||
arena: &'a Bump,
|
||||
subs: &'a Subs,
|
||||
module: &'ctx Module<'ctx>,
|
||||
context: &'ctx Context,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Expr<'a> {
|
||||
// Literals
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Str(&'a str),
|
||||
|
||||
// Load/Store
|
||||
Load(InlinableString),
|
||||
Store(&'a [(InlinableString, Variable, Expr<'a>)], &'a Expr<'a>),
|
||||
|
||||
// Functions
|
||||
FunctionPointer(InlinableString),
|
||||
CallByPointer(InlinableString, &'a [Expr<'a>]),
|
||||
CallByName(InlinableString, &'a [Expr<'a>]),
|
||||
|
||||
// Exactly two conditional branches, e.g. if/else
|
||||
Cond {
|
||||
// The left-hand side of the conditional comparison and the right-hand side.
|
||||
// These are stored separately because there are different machine instructions
|
||||
// for e.g. "compare float and jump" vs. "compare integer and jump"
|
||||
cond_lhs: &'a Expr<'a>,
|
||||
cond_rhs: &'a Expr<'a>,
|
||||
// What to do if the condition either passes or fails
|
||||
pass: &'a Expr<'a>,
|
||||
fail: &'a Expr<'a>,
|
||||
ret_var: Variable,
|
||||
},
|
||||
/// More than two conditional branches, e.g. a 3-way when-expression
|
||||
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,
|
||||
},
|
||||
|
||||
Struct(&'a [(InlinableString, Expr<'a>)]),
|
||||
|
||||
RuntimeError(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> Expr<'a> {
|
||||
pub fn new<'ctx>(
|
||||
arena: &'a Bump,
|
||||
subs: &'a Subs,
|
||||
module: &'ctx Module<'ctx>,
|
||||
context: &'ctx Context,
|
||||
can_expr: can::expr::Expr,
|
||||
procs: &mut Procs<'a, 'ctx>,
|
||||
) -> Self {
|
||||
let env = Env {
|
||||
arena,
|
||||
subs,
|
||||
module,
|
||||
context,
|
||||
};
|
||||
|
||||
from_can(&env, can_expr, procs, None)
|
||||
}
|
||||
}
|
||||
|
||||
fn from_can<'a, 'ctx>(
|
||||
env: &Env<'a, 'ctx>,
|
||||
can_expr: can::expr::Expr,
|
||||
procs: &mut Procs<'a, 'ctx>,
|
||||
name: Option<InlinableString>,
|
||||
) -> Expr<'a> {
|
||||
use crate::can::expr::Expr::*;
|
||||
use crate::can::pattern::Pattern::*;
|
||||
|
||||
match can_expr {
|
||||
Int(_, val) => Expr::Int(val),
|
||||
Float(_, val) => Expr::Float(val),
|
||||
Str(string) | BlockStr(string) => Expr::Str(env.arena.alloc(string)),
|
||||
Var {
|
||||
resolved_symbol, ..
|
||||
} => Expr::Load(resolved_symbol.into()),
|
||||
LetNonRec(def, ret_expr, _) => {
|
||||
let arena = env.arena;
|
||||
let loc_pattern = def.loc_pattern;
|
||||
let loc_expr = def.loc_expr;
|
||||
let mut stored = Vec::with_capacity_in(1, arena);
|
||||
|
||||
// If we're defining a named closure, insert it into Procs and then
|
||||
// remove the Let. When code gen later goes to look it up, it'll be in Procs!
|
||||
//
|
||||
// Before:
|
||||
//
|
||||
// identity = \a -> a
|
||||
//
|
||||
// identity 5
|
||||
//
|
||||
// After: (`identity` is now in Procs)
|
||||
//
|
||||
// identity 5
|
||||
//
|
||||
match &loc_pattern.value {
|
||||
Identifier(name) => {
|
||||
match &loc_expr.value {
|
||||
Closure(_, _, _, _, _) => {
|
||||
// Extract Procs, but discard the resulting Expr::Load.
|
||||
// That Load looks up the pointer, which we won't use here!
|
||||
from_can(env, loc_expr.value, procs, Some(name.clone().into()));
|
||||
|
||||
// Discard this LetNonRec by replacing it with its ret_expr.
|
||||
return from_can(env, ret_expr.value, procs, None);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// If it wasn't specifically an Identifier & Closure, proceed as normal.
|
||||
store_pattern(
|
||||
env,
|
||||
loc_pattern.value,
|
||||
loc_expr.value,
|
||||
def.expr_var,
|
||||
procs,
|
||||
&mut stored,
|
||||
);
|
||||
|
||||
// At this point, it's safe to assume we aren't assigning a Closure to a def.
|
||||
// Extract Procs from the def body and the ret expression, and return the result!
|
||||
let ret = from_can(env, ret_expr.value, procs, None);
|
||||
|
||||
Expr::Store(stored.into_bump_slice(), arena.alloc(ret))
|
||||
}
|
||||
|
||||
Closure(_, _symbol, _, loc_args, boxed_body) => {
|
||||
let (loc_body, ret_var) = *boxed_body;
|
||||
let name = name.unwrap_or_else(||
|
||||
// Give the closure a name like "_0" or "_1".
|
||||
// We know procs.len() will be unique!
|
||||
format!("_{}", procs.len()).into());
|
||||
|
||||
add_closure(env, name, loc_body.value, ret_var, &loc_args, procs)
|
||||
}
|
||||
|
||||
Call(boxed, loc_args, _) => {
|
||||
let (_, loc_expr, _) = *boxed;
|
||||
let mut args = Vec::with_capacity_in(loc_args.len(), env.arena);
|
||||
|
||||
for (_, loc_arg) in loc_args {
|
||||
args.push(from_can(env, loc_arg.value, procs, None));
|
||||
}
|
||||
|
||||
match from_can(env, loc_expr.value, procs, None) {
|
||||
Expr::Load(proc_name) => Expr::CallByName(proc_name, args.into_bump_slice()),
|
||||
Expr::FunctionPointer(proc_name) => {
|
||||
// Call by pointer - the closure was anonymous, e.g.
|
||||
//
|
||||
// ((\a -> a) 5)
|
||||
//
|
||||
// It might even be the anonymous result of a conditional:
|
||||
//
|
||||
// ((if x > 0 then \a -> a else \_ -> 0) 5)
|
||||
Expr::CallByPointer(proc_name, args.into_bump_slice())
|
||||
}
|
||||
non_ptr => {
|
||||
panic!(
|
||||
"Tried to call by pointer, but encountered a non-pointer: {:?}",
|
||||
non_ptr
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
When {
|
||||
expr_var,
|
||||
loc_cond,
|
||||
branches,
|
||||
..
|
||||
} => {
|
||||
debug_assert!(!branches.is_empty());
|
||||
|
||||
if branches.len() == 2 {
|
||||
let arena = env.arena;
|
||||
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), 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));
|
||||
let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
|
||||
|
||||
Expr::Cond {
|
||||
cond_lhs,
|
||||
cond_rhs,
|
||||
pass,
|
||||
fail,
|
||||
ret_var: expr_var,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
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.
|
||||
let arena = env.arena;
|
||||
let mut stored = Vec::with_capacity_in(1, arena);
|
||||
let (loc_pattern, loc_branch) = branches.into_iter().next().unwrap();
|
||||
|
||||
store_pattern(
|
||||
env,
|
||||
loc_pattern.value,
|
||||
loc_branch.value,
|
||||
expr_var,
|
||||
procs,
|
||||
&mut stored,
|
||||
);
|
||||
|
||||
let ret = from_can(env, loc_cond.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 {
|
||||
// /// 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 more than 2 branches.");
|
||||
}
|
||||
}
|
||||
|
||||
other => panic!("TODO convert canonicalized {:?} to ll::Expr", other),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_closure<'a, 'ctx>(
|
||||
env: &Env<'a, 'ctx>,
|
||||
name: InlinableString,
|
||||
can_body: can::expr::Expr,
|
||||
ret_var: Variable,
|
||||
loc_args: &[(Variable, Located<Pattern>)],
|
||||
procs: &mut Procs<'a, 'ctx>,
|
||||
) -> Expr<'a> {
|
||||
let subs = &env.subs;
|
||||
let context = env.context;
|
||||
let arena = env.arena;
|
||||
let ret_content = subs.get_without_compacting(ret_var).content;
|
||||
let ret_type = content_to_basic_type(&ret_content, subs, context).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Error converting function return value content to basic type: {:?}",
|
||||
err
|
||||
)
|
||||
});
|
||||
|
||||
let mut arg_names = Vec::with_capacity_in(loc_args.len(), arena);
|
||||
let mut arg_basic_types = Vec::with_capacity_in(loc_args.len(), arena);
|
||||
let mut proc_args = Vec::with_capacity_in(loc_args.len(), arena);
|
||||
|
||||
for (arg_var, loc_arg) in loc_args.iter() {
|
||||
let content = subs.get_without_compacting(*arg_var).content;
|
||||
|
||||
arg_basic_types.push(
|
||||
content_to_basic_type(&content, subs, context).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Error converting function arg content to basic type: {:?}",
|
||||
err
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
||||
let layout = match Layout::from_content(arena, content, subs) {
|
||||
Ok(layout) => layout,
|
||||
Err(()) => {
|
||||
return invalid_closure(env, name, ret_type, procs);
|
||||
}
|
||||
};
|
||||
|
||||
let arg_name: InlinableString = match &loc_arg.value {
|
||||
Pattern::Identifier(name) => name.as_str().into(),
|
||||
_ => {
|
||||
panic!("TODO determine arg_name for pattern {:?}", loc_arg.value);
|
||||
}
|
||||
};
|
||||
|
||||
arg_names.push(arg_name.clone());
|
||||
proc_args.push((layout, arg_name));
|
||||
}
|
||||
|
||||
let fn_type = ret_type.fn_type(arg_basic_types.into_bump_slice(), false);
|
||||
let fn_val = env.module.add_function(&name, fn_type, None);
|
||||
let proc = Proc {
|
||||
args: proc_args.into_bump_slice(),
|
||||
body: from_can(env, can_body, procs, None),
|
||||
closes_over: Layout::ZeroSized,
|
||||
};
|
||||
|
||||
procs.insert(name.clone(), (Some(proc), fn_val));
|
||||
|
||||
Expr::FunctionPointer(name)
|
||||
}
|
||||
|
||||
fn invalid_closure<'a, 'ctx>(
|
||||
env: &Env<'a, 'ctx>,
|
||||
name: InlinableString,
|
||||
ret_type: BasicTypeEnum<'ctx>,
|
||||
procs: &mut Procs<'a, 'ctx>,
|
||||
) -> Expr<'a> {
|
||||
let fn_type = ret_type.fn_type(&[], false);
|
||||
let fn_val = env.module.add_function(&name, fn_type, None);
|
||||
|
||||
procs.insert(name.clone(), (None, fn_val));
|
||||
|
||||
Expr::FunctionPointer(name)
|
||||
}
|
||||
|
||||
fn store_pattern<'a, 'ctx>(
|
||||
env: &Env<'a, 'ctx>,
|
||||
can_pat: Pattern,
|
||||
can_expr: can::expr::Expr,
|
||||
var: Variable,
|
||||
procs: &mut Procs<'a, 'ctx>,
|
||||
stored: &mut Vec<'a, (InlinableString, Variable, Expr<'a>)>,
|
||||
) {
|
||||
use crate::can::pattern::Pattern::*;
|
||||
|
||||
// If we're defining a named closure, insert it into Procs and then
|
||||
// remove the Let. When code gen later goes to look it up, it'll be in Procs!
|
||||
//
|
||||
// Before:
|
||||
//
|
||||
// identity = \a -> a
|
||||
//
|
||||
// identity 5
|
||||
//
|
||||
// After: (`identity` is now in Procs)
|
||||
//
|
||||
// identity 5
|
||||
//
|
||||
match can_pat {
|
||||
Identifier(name) => stored.push((name.into(), var, from_can(env, can_expr, procs, None))),
|
||||
Underscore => {
|
||||
// Since _ is never read, it's safe to reassign it.
|
||||
stored.push(("_".into(), var, from_can(env, can_expr, procs, None)))
|
||||
}
|
||||
_ => {
|
||||
panic!("TODO store_pattern for {:?}", can_pat);
|
||||
}
|
||||
}
|
||||
}
|
95
src/ll/layout.rs
Normal file
95
src/ll/layout.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
use crate::subs::{Content, FlatType, Subs};
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use inlinable_string::InlinableString;
|
||||
|
||||
/// Types for code gen must be monomorphic. No type variables allowed!
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Layout<'a> {
|
||||
/// A unary type like {} or [ NoOtherTags ] - this does not need to be a value at runtime.
|
||||
ZeroSized,
|
||||
/// A function. The types of its arguments, then the type of its return value.
|
||||
FunctionPointer(&'a [Layout<'a>], &'a Layout<'a>),
|
||||
Struct(&'a [(InlinableString, Layout<'a>)]),
|
||||
|
||||
/// Applying a type to some arguments (e.g. Map.Map String Int)
|
||||
Apply {
|
||||
module_name: InlinableString,
|
||||
name: InlinableString,
|
||||
args: &'a [Layout<'a>],
|
||||
},
|
||||
|
||||
Pointer(&'a Layout<'a>),
|
||||
}
|
||||
|
||||
impl<'a> Layout<'a> {
|
||||
/// Returns Err(()) if given an error, or Ok(Layout) if given a non-erroneous Structure.
|
||||
/// Panics if given a FlexVar or RigidVar, since those should have been
|
||||
/// monomorphized away already!
|
||||
pub fn from_content(arena: &'a Bump, content: Content, subs: &Subs) -> Result<Self, ()> {
|
||||
use crate::subs::Content::*;
|
||||
|
||||
match content {
|
||||
var @ FlexVar(_) | var @ RigidVar(_) => {
|
||||
panic!("Layout::from_content encountered an unresolved {:?}", var);
|
||||
}
|
||||
Structure(flat_type) => layout_from_flat_type(arena, flat_type, subs),
|
||||
Alias(_, _, _, _) => {
|
||||
panic!("TODO recursively resolve type aliases in Layout::from_content");
|
||||
}
|
||||
Error => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_from_flat_type<'a>(
|
||||
arena: &'a Bump,
|
||||
flat_type: FlatType,
|
||||
subs: &Subs,
|
||||
) -> Result<Layout<'a>, ()> {
|
||||
use crate::subs::FlatType::*;
|
||||
|
||||
match flat_type {
|
||||
Apply {
|
||||
module_name,
|
||||
name,
|
||||
args,
|
||||
} => {
|
||||
let mut layout_args = Vec::with_capacity_in(args.len(), arena);
|
||||
|
||||
for arg_var in args {
|
||||
let arg_content = subs.get_without_compacting(arg_var).content;
|
||||
|
||||
layout_args.push(Layout::from_content(arena, arg_content, subs)?);
|
||||
}
|
||||
|
||||
Ok(Layout::Apply {
|
||||
module_name: module_name.as_str().into(),
|
||||
name: name.as_str().into(),
|
||||
args: layout_args.into_bump_slice(),
|
||||
})
|
||||
}
|
||||
Func(args, ret_var) => {
|
||||
let mut fn_args = Vec::with_capacity_in(args.len(), arena);
|
||||
|
||||
for arg_var in args {
|
||||
let arg_content = subs.get_without_compacting(arg_var).content;
|
||||
|
||||
fn_args.push(Layout::from_content(arena, arg_content, subs)?);
|
||||
}
|
||||
|
||||
let ret_content = subs.get_without_compacting(ret_var).content;
|
||||
let ret = Layout::from_content(arena, ret_content, subs)?;
|
||||
|
||||
Ok(Layout::FunctionPointer(
|
||||
fn_args.into_bump_slice(),
|
||||
arena.alloc(ret),
|
||||
))
|
||||
}
|
||||
Record(_, _) => {
|
||||
panic!("TODO make Layout for non-empty Record");
|
||||
}
|
||||
Erroneous(_) => Err(()),
|
||||
EmptyRecord => Ok(Layout::Struct(&[])),
|
||||
}
|
||||
}
|
2
src/ll/mod.rs
Normal file
2
src/ll/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod expr;
|
||||
pub mod layout;
|
|
@ -12,11 +12,12 @@ mod helpers;
|
|||
#[cfg(test)]
|
||||
mod test_gen {
|
||||
use crate::helpers::can_expr;
|
||||
use bumpalo::Bump;
|
||||
use inkwell::context::Context;
|
||||
use inkwell::execution_engine::JitFunction;
|
||||
use inkwell::types::BasicType;
|
||||
use inkwell::OptimizationLevel;
|
||||
use roc::gen::compile::compile_standalone_expr;
|
||||
use roc::gen::build::build_can_expr;
|
||||
use roc::gen::convert::content_to_basic_type;
|
||||
use roc::gen::env::Env;
|
||||
use roc::infer::infer_expr;
|
||||
|
@ -24,6 +25,7 @@ mod test_gen {
|
|||
|
||||
macro_rules! assert_evals_to {
|
||||
($src:expr, $expected:expr, $ty:ty) => {
|
||||
let arena = Bump::new();
|
||||
let (expr, _output, _problems, var_store, variable, constraint) = can_expr($src);
|
||||
let mut subs = Subs::new(var_store.into());
|
||||
let mut unify_problems = Vec::new();
|
||||
|
@ -49,9 +51,9 @@ mod test_gen {
|
|||
subs,
|
||||
builder: &builder,
|
||||
context: &context,
|
||||
module: &module,
|
||||
module: arena.alloc(module),
|
||||
};
|
||||
let ret = compile_standalone_expr(&env, function, &expr);
|
||||
let ret = build_can_expr(&env, function, expr);
|
||||
|
||||
builder.build_return(Some(&ret));
|
||||
|
||||
|
@ -229,37 +231,66 @@ mod test_gen {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gen_basic_fn() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
always42 : Num.Num Int.Integer -> Num.Num Int.Integer
|
||||
always42 = \num -> 42
|
||||
// #[test]
|
||||
// fn gen_basic_fn() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// always42 : Num.Num Int.Integer -> Num.Num Int.Integer
|
||||
// always42 = \num -> 42
|
||||
|
||||
always42 5
|
||||
"#
|
||||
),
|
||||
42,
|
||||
i64
|
||||
);
|
||||
}
|
||||
// always42 5
|
||||
// "#
|
||||
// ),
|
||||
// 42,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn gen_when_fn() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
limitedNegate = \num ->
|
||||
when num is
|
||||
1 -> -1
|
||||
0 -> 0
|
||||
// #[test]
|
||||
// fn gen_when_fn() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// limitedNegate = \num ->
|
||||
// when num is
|
||||
// 1 -> -1
|
||||
// 0 -> 0
|
||||
|
||||
limitedNegate 1
|
||||
"#
|
||||
),
|
||||
42,
|
||||
i64
|
||||
);
|
||||
}
|
||||
// limitedNegate 1
|
||||
// "#
|
||||
// ),
|
||||
// 42,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn apply_unnamed_fn() {
|
||||
// assert_evals_to!(
|
||||
// // We could improve the perf of this scenario by
|
||||
// indoc!(
|
||||
// r#"
|
||||
// (\a -> a) 5
|
||||
// "#
|
||||
// ),
|
||||
// 5,
|
||||
// i64
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn return_unnamed_fn() {
|
||||
// assert_evals_to!(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// alwaysIdentity = \_ -> (\a -> a)
|
||||
|
||||
// (alwaysIdentity 1) 3.14
|
||||
// "#
|
||||
// ),
|
||||
// 3.14,
|
||||
// f64
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue