Merge pull request #355 from rtfeldman/call-by-layout

Call by (name + layout) in code gen
This commit is contained in:
Richard Feldman 2020-05-10 15:51:59 -04:00 committed by GitHub
commit f688236118
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 769 additions and 712 deletions

View File

@ -1,5 +1,6 @@
#[macro_use]
extern crate clap;
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::module::Linkage;
@ -8,6 +9,7 @@ use inkwell::types::BasicType;
use inkwell::OptimizationLevel;
use roc_collections::all::ImMap;
use roc_collections::all::MutMap;
use roc_gen::layout_id::LayoutIds;
use roc_gen::llvm::build::{
build_proc, build_proc_header, get_call_conventions, module_from_builtins, OptLevel,
};
@ -358,7 +360,7 @@ fn gen(
// Compute main_fn_type before moving subs to Env
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let layout = Layout::from_content(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| {
let layout = Layout::new(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| {
panic!(
"Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}",
err, subs
@ -378,9 +380,10 @@ fn gen(
module: arena.alloc(module),
ptr_bytes,
};
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
let mut layout_ids = LayoutIds::default();
let mut procs = Procs::default();
let mut mono_problems = std::vec::Vec::new();
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
let mut mono_env = Env {
arena,
subs: &mut subs,
@ -451,13 +454,17 @@ fn gen(
env.interns.all_ident_ids.insert(home, ident_ids);
let mut headers = Vec::with_capacity(procs.len());
let (mut proc_map, runtime_errors) = procs.into_map();
assert_eq!(runtime_errors, roc_collections::all::MutSet::default());
// Add all the Proc headers to the module.
// We have to do this in a separate pass first,
// because their bodies may reference each other.
for (symbol, opt_proc) in procs.as_map().into_iter() {
if let Some(proc) = opt_proc {
let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc);
for (symbol, mut procs_by_layout) in proc_map.drain() {
for (layout, proc) in procs_by_layout.drain() {
let (fn_val, arg_basic_types) =
build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
headers.push((proc, fn_val, arg_basic_types));
}
@ -469,7 +476,7 @@ fn gen(
// (This approach means we don't have to defensively clone name here.)
//
// println!("\n\nBuilding and then verifying function {}\n\n", name);
build_proc(&env, proc, &procs, fn_val, arg_basic_types);
build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types);
if fn_val.verify(true) {
fpm.run_on(&fn_val);
@ -495,10 +502,10 @@ fn gen(
let ret = roc_gen::llvm::build::build_expr(
&env,
&mut layout_ids,
&ImMap::default(),
main_fn,
&main_body,
&Procs::default(),
);
builder.build_return(Some(&ret));

View File

@ -14,6 +14,7 @@ use roc_can::scope::Scope;
use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet};
use roc_constrain::expr::constrain_expr;
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
use roc_gen::layout_id::LayoutIds;
use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_module::ident::Ident;
@ -218,7 +219,7 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St
let expr_type_str = content_to_string(content.clone(), &subs, home, &interns);
// Compute main_fn_type before moving subs to Env
let layout = Layout::from_content(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| {
let layout = Layout::new(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| {
panic!(
"Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}",
err, subs
@ -243,6 +244,7 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St
};
let mut procs = Procs::default();
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
let mut layout_ids = LayoutIds::default();
// Populate Procs and get the low-level Expr from the canonical Expr
let mut mono_problems = Vec::new();
@ -261,13 +263,21 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St
env.interns.all_ident_ids.insert(home, ident_ids);
let mut headers = Vec::with_capacity(procs.len());
let (mut proc_map, runtime_errors) = procs.into_map();
assert_eq!(
runtime_errors,
roc_collections::all::MutSet::default(),
"TODO code gen runtime exception functions"
);
// Add all the Proc headers to the module.
// We have to do this in a separate pass first,
// because their bodies may reference each other.
for (symbol, opt_proc) in procs.as_map().into_iter() {
if let Some(proc) = opt_proc {
let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc);
for (symbol, mut procs_by_layout) in proc_map.drain() {
for (layout, proc) in procs_by_layout.drain() {
let (fn_val, arg_basic_types) =
build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
headers.push((proc, fn_val, arg_basic_types));
}
@ -279,7 +289,7 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St
// (This approach means we don't have to defensively clone name here.)
//
// println!("\n\nBuilding and then verifying function {}\n\n", name);
build_proc(&env, proc, &procs, fn_val, arg_basic_types);
build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types);
if fn_val.verify(true) {
fpm.run_on(&fn_val);
@ -305,10 +315,10 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St
let ret = roc_gen::llvm::build::build_expr(
&env,
&mut layout_ids,
&ImMap::default(),
main_fn,
&main_body,
&Procs::default(),
);
builder.build_return(Some(&ret));

View File

@ -0,0 +1,51 @@
use roc_collections::all::{default_hasher, MutMap};
use roc_module::symbol::{Interns, Symbol};
use roc_mono::layout::Layout;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LayoutId(u32);
impl LayoutId {
// Returns something like "foo#1" when given a symbol that interns to "foo"
// and a LayoutId of 1.
pub fn to_symbol_string(self, symbol: Symbol, interns: &Interns) -> String {
format!("{}#{}", symbol.ident_string(interns), self.0)
}
}
struct IdsByLayout<'a> {
by_id: MutMap<Layout<'a>, u32>,
next_id: u32,
}
#[derive(Default)]
pub struct LayoutIds<'a> {
by_symbol: MutMap<Symbol, IdsByLayout<'a>>,
}
impl<'a> LayoutIds<'a> {
/// Returns a LayoutId which is unique for the given symbol and layout.
/// If given the same symbol and same layout, returns the same LayoutId.
pub fn get(&mut self, symbol: Symbol, layout: &Layout<'a>) -> LayoutId {
// Note: this function does some weird stuff to satisfy the borrow checker.
// There's probably a nicer way to write it that still works.
let ids = self.by_symbol.entry(symbol).or_insert_with(|| IdsByLayout {
by_id: HashMap::with_capacity_and_hasher(1, default_hasher()),
next_id: 1,
});
// Get the id associated with this layout, or default to next_id.
let answer = ids.by_id.get(layout).copied().unwrap_or(ids.next_id);
// If we had to default to next_id, it must not have been found;
// store the ID we're going to return and increment next_id.
if answer == ids.next_id {
ids.by_id.insert(layout.clone(), ids.next_id);
ids.next_id += 1;
}
LayoutId(answer)
}
}

View File

@ -11,4 +11,5 @@
// re-enable this when working on performance optimizations than have it block PRs.
#![allow(clippy::large_enum_variant)]
pub mod layout_id;
pub mod llvm;

View File

@ -1,3 +1,4 @@
use crate::layout_id::LayoutIds;
use crate::llvm::convert::{
basic_type_from_layout, collection, get_fn_type, get_ptr_type, ptr_int,
};
@ -15,7 +16,7 @@ use inkwell::AddressSpace;
use inkwell::{FloatPredicate, IntPredicate, OptimizationLevel};
use roc_collections::all::ImMap;
use roc_module::symbol::{Interns, Symbol};
use roc_mono::expr::{Expr, Proc, Procs};
use roc_mono::expr::{Expr, Proc};
use roc_mono::layout::{Builtin, Layout};
use target_lexicon::CallingConvention;
@ -144,10 +145,10 @@ pub fn add_passes(fpm: &PassManager<FunctionValue<'_>>, opt_level: OptLevel) {
#[allow(clippy::cognitive_complexity)]
pub fn build_expr<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
expr: &'a Expr<'a>,
procs: &Procs<'a>,
expr: &Expr<'a>,
) -> BasicValueEnum<'ctx> {
use roc_mono::expr::Expr::*;
@ -166,14 +167,53 @@ pub fn build_expr<'a, 'ctx, 'env>(
let pass = env.arena.alloc(Expr::Store(pass_stores, pass_expr));
let fail = env.arena.alloc(Expr::Store(fail_stores, fail_expr));
let conditional = Branch2 {
cond: branch_symbol,
pass,
fail,
ret_layout: ret_layout.clone(),
};
let ret_type =
basic_type_from_layout(env.arena, env.context, &ret_layout, env.ptr_bytes);
build_branch2(env, scope, parent, conditional, procs)
let cond_expr = load_symbol(env, scope, branch_symbol);
match cond_expr {
IntValue(value) => {
// This is a call tobuild_basic_phi2, except inlined to prevent
// problems with lifetimes and closures involving layout_ids.
let builder = env.builder;
let context = env.context;
// build blocks
let then_block = context.append_basic_block(parent, "then");
let else_block = context.append_basic_block(parent, "else");
let cont_block = context.append_basic_block(parent, "branchcont");
builder.build_conditional_branch(value, then_block, else_block);
// build then block
builder.position_at_end(then_block);
let then_val = build_expr(env, layout_ids, scope, parent, pass);
builder.build_unconditional_branch(cont_block);
let then_block = builder.get_insert_block().unwrap();
// build else block
builder.position_at_end(else_block);
let else_val = build_expr(env, layout_ids, scope, parent, fail);
builder.build_unconditional_branch(cont_block);
let else_block = builder.get_insert_block().unwrap();
// emit merge block
builder.position_at_end(cont_block);
let phi = builder.build_phi(ret_type, "branch");
phi.add_incoming(&[(&then_val, then_block), (&else_val, else_block)]);
phi.as_basic_value()
}
_ => panic!(
"Tried to make a branch out of an invalid condition: cond_expr = {:?}",
cond_expr,
),
}
}
Switch {
cond,
@ -201,14 +241,14 @@ pub fn build_expr<'a, 'ctx, 'env>(
ret_type,
};
build_switch(env, scope, parent, switch_args, procs)
build_switch(env, layout_ids, scope, parent, switch_args)
}
Store(stores, ret) => {
let mut scope = im_rc::HashMap::clone(scope);
let context = &env.context;
for (symbol, layout, expr) in stores.iter() {
let val = build_expr(env, &scope, parent, &expr, procs);
let val = build_expr(env, layout_ids, &scope, parent, &expr);
let expr_bt = basic_type_from_layout(env.arena, context, &layout, env.ptr_bytes);
let alloca = create_entry_block_alloca(
env,
@ -229,16 +269,17 @@ pub fn build_expr<'a, 'ctx, 'env>(
scope.insert(*symbol, (layout.clone(), alloca));
}
build_expr(env, &scope, parent, ret, procs)
build_expr(env, layout_ids, &scope, parent, ret)
}
CallByName(symbol, args) => match *symbol {
CallByName { name, layout, args } => match *name {
Symbol::BOOL_OR => {
// The (||) operator
debug_assert!(args.len() == 2);
let comparison = build_expr(env, scope, parent, &args[0].0, procs).into_int_value();
let comparison =
build_expr(env, layout_ids, scope, parent, &args[0].0).into_int_value();
let build_then = || env.context.bool_type().const_int(true as u64, false).into();
let build_else = || build_expr(env, scope, parent, &args[1].0, procs);
let build_else = || build_expr(env, layout_ids, scope, parent, &args[1].0);
let ret_type = env.context.bool_type().into();
@ -248,8 +289,9 @@ pub fn build_expr<'a, 'ctx, 'env>(
// The (&&) operator
debug_assert!(args.len() == 2);
let comparison = build_expr(env, scope, parent, &args[0].0, procs).into_int_value();
let build_then = || build_expr(env, scope, parent, &args[1].0, procs);
let comparison =
build_expr(env, layout_ids, scope, parent, &args[0].0).into_int_value();
let build_then = || build_expr(env, layout_ids, scope, parent, &args[1].0);
let build_else = || {
env.context
.bool_type()
@ -265,7 +307,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
// The (!) operator
debug_assert!(args.len() == 1);
let arg = build_expr(env, scope, parent, &args[0].0, procs);
let arg = build_expr(env, layout_ids, scope, parent, &args[0].0);
let int_val = env.builder.build_not(arg.into_int_value(), "bool_not");
@ -275,17 +317,27 @@ pub fn build_expr<'a, 'ctx, 'env>(
let mut arg_tuples: Vec<(BasicValueEnum, &'a Layout<'a>)> =
Vec::with_capacity_in(args.len(), env.arena);
for (arg, layout) in args.iter() {
arg_tuples.push((build_expr(env, scope, parent, arg, procs), layout));
for (arg, arg_layout) in args.iter() {
arg_tuples.push((build_expr(env, layout_ids, scope, parent, arg), arg_layout));
}
call_with_args(*symbol, parent, arg_tuples.into_bump_slice(), env)
call_with_args(
env,
layout_ids,
layout,
*name,
parent,
arg_tuples.into_bump_slice(),
)
}
},
FunctionPointer(symbol) => {
FunctionPointer(symbol, layout) => {
let fn_name = layout_ids
.get(*symbol, layout)
.to_symbol_string(*symbol, &env.interns);
let ptr = env
.module
.get_function(symbol.ident_string(&env.interns))
.get_function(fn_name.as_str())
.unwrap_or_else(|| panic!("Could not get pointer to unknown function {:?}", symbol))
.as_global_value()
.as_pointer_value();
@ -296,10 +348,10 @@ pub fn build_expr<'a, 'ctx, 'env>(
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), env.arena);
for arg in args.iter() {
arg_vals.push(build_expr(env, scope, parent, arg, procs));
arg_vals.push(build_expr(env, layout_ids, scope, parent, arg));
}
let call = match build_expr(env, scope, parent, sub_expr, procs) {
let call = match build_expr(env, layout_ids, scope, parent, sub_expr) {
BasicValueEnum::PointerValue(ptr) => {
env.builder.build_call(ptr, arg_vals.as_slice(), "tmp")
}
@ -393,7 +445,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
let index_val = ctx.i32_type().const_int(index as u64, false);
let elem_ptr =
unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") };
let val = build_expr(env, &scope, parent, &elem, procs);
let val = build_expr(env, layout_ids, &scope, parent, &elem);
builder.build_store(elem_ptr, val);
}
@ -439,7 +491,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
let mut field_vals = Vec::with_capacity_in(num_fields, env.arena);
for (field_expr, field_layout) in sorted_fields.iter() {
let val = build_expr(env, &scope, parent, field_expr, procs);
let val = build_expr(env, layout_ids, &scope, parent, field_expr);
let field_type =
basic_type_from_layout(env.arena, env.context, &field_layout, env.ptr_bytes);
@ -476,7 +528,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
let mut field_vals = Vec::with_capacity_in(num_fields, env.arena);
for (field_expr, field_layout) in it {
let val = build_expr(env, &scope, parent, field_expr, procs);
let val = build_expr(env, layout_ids, &scope, parent, field_expr);
let field_type =
basic_type_from_layout(env.arena, env.context, &field_layout, env.ptr_bytes);
@ -516,7 +568,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
let mut field_vals = Vec::with_capacity_in(num_fields, env.arena);
for (field_expr, field_layout) in arguments.iter() {
let val = build_expr(env, &scope, parent, field_expr, procs);
let val = build_expr(env, layout_ids, &scope, parent, field_expr);
let field_type =
basic_type_from_layout(env.arena, env.context, &field_layout, ptr_size);
@ -594,7 +646,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
// Get Struct val
// Since this is a one-element tag union, we get the correct struct immediately
let argument = build_expr(env, &scope, parent, expr, procs).into_struct_value();
let argument = build_expr(env, layout_ids, &scope, parent, expr).into_struct_value();
builder
.build_extract_value(
@ -630,7 +682,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
.struct_type(field_types.into_bump_slice(), false);
// cast the argument bytes into the desired shape for this tag
let argument = build_expr(env, &scope, parent, expr, procs).into_struct_value();
let argument = build_expr(env, layout_ids, &scope, parent, expr).into_struct_value();
let struct_value = cast_struct_struct(builder, argument, struct_type);
@ -705,41 +757,6 @@ fn extract_tag_discriminant<'a, 'ctx, 'env>(
.into_int_value()
}
struct Branch2<'a> {
cond: &'a Symbol,
pass: &'a Expr<'a>,
fail: &'a Expr<'a>,
ret_layout: Layout<'a>,
}
fn build_branch2<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
cond: Branch2<'a>,
procs: &Procs<'a>,
) -> BasicValueEnum<'ctx> {
let ret_layout = cond.ret_layout;
let pass = cond.pass;
let fail = cond.fail;
let ret_type = basic_type_from_layout(env.arena, env.context, &ret_layout, env.ptr_bytes);
let cond_expr = load_symbol(env, scope, cond.cond);
match cond_expr {
IntValue(value) => {
let build_then = || build_expr(env, scope, parent, pass, procs);
let build_else = || build_expr(env, scope, parent, fail, procs);
build_basic_phi2(env, parent, value, build_then, build_else, ret_type)
}
_ => panic!(
"Tried to make a branch out of an invalid condition: cond_expr = {:?}",
cond_expr,
),
}
}
struct SwitchArgs<'a, 'ctx> {
pub cond_expr: &'a Expr<'a>,
pub cond_layout: Layout<'a>,
@ -750,10 +767,10 @@ struct SwitchArgs<'a, 'ctx> {
fn build_switch<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
switch_args: SwitchArgs<'a, 'ctx>,
procs: &Procs<'a>,
) -> BasicValueEnum<'ctx> {
let arena = env.arena;
let builder = env.builder;
@ -774,7 +791,7 @@ fn build_switch<'a, 'ctx, 'env>(
Layout::Builtin(Builtin::Float64) => {
// float matches are done on the bit pattern
cond_layout = Layout::Builtin(Builtin::Int64);
let full_cond = build_expr(env, scope, parent, cond_expr, procs);
let full_cond = build_expr(env, layout_ids, scope, parent, cond_expr);
builder
.build_bitcast(full_cond, env.context.i64_type(), "")
@ -783,11 +800,14 @@ fn build_switch<'a, 'ctx, 'env>(
Layout::Union(_) => {
// we match on the discriminant, not the whole Tag
cond_layout = Layout::Builtin(Builtin::Int64);
let full_cond = build_expr(env, scope, parent, cond_expr, procs).into_struct_value();
let full_cond =
build_expr(env, layout_ids, scope, parent, cond_expr).into_struct_value();
extract_tag_discriminant(env, full_cond)
}
Layout::Builtin(_) => build_expr(env, scope, parent, cond_expr, procs).into_int_value(),
Layout::Builtin(_) => {
build_expr(env, layout_ids, scope, parent, cond_expr).into_int_value()
}
other => todo!("Build switch value from layout: {:?}", other),
};
@ -824,7 +844,7 @@ fn build_switch<'a, 'ctx, 'env>(
for ((_, branch_expr), (_, block)) in branches.iter().zip(cases) {
builder.position_at_end(block);
let branch_val = build_expr(env, scope, parent, branch_expr, procs);
let branch_val = build_expr(env, layout_ids, scope, parent, branch_expr);
builder.build_unconditional_branch(cont_block);
@ -834,7 +854,7 @@ fn build_switch<'a, 'ctx, 'env>(
// The block for the conditional's default branch.
builder.position_at_end(default_block);
let default_val = build_expr(env, scope, parent, default_branch, procs);
let default_val = build_expr(env, layout_ids, scope, parent, default_branch);
builder.build_unconditional_branch(cont_block);
@ -852,40 +872,17 @@ fn build_switch<'a, 'ctx, 'env>(
phi.as_basic_value()
}
// TODO trim down these arguments
#[allow(dead_code)]
#[allow(clippy::too_many_arguments)]
fn build_expr_phi2<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
comparison: IntValue<'ctx>,
pass: &'a Expr<'a>,
fail: &'a Expr<'a>,
ret_type: BasicTypeEnum<'ctx>,
procs: &Procs<'a>,
) -> BasicValueEnum<'ctx> {
build_basic_phi2(
env,
parent,
comparison,
|| build_expr(env, scope, parent, pass, procs),
|| build_expr(env, scope, parent, fail, procs),
ret_type,
)
}
fn build_basic_phi2<'a, 'ctx, 'env, PassFn, FailFn>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
comparison: IntValue<'ctx>,
build_pass: PassFn,
build_fail: FailFn,
mut build_pass: PassFn,
mut build_fail: FailFn,
ret_type: BasicTypeEnum<'ctx>,
) -> BasicValueEnum<'ctx>
where
PassFn: Fn() -> BasicValueEnum<'ctx>,
FailFn: Fn() -> BasicValueEnum<'ctx>,
PassFn: FnMut() -> BasicValueEnum<'ctx>,
FailFn: FnMut() -> BasicValueEnum<'ctx>,
{
let builder = env.builder;
let context = env.context;
@ -953,7 +950,9 @@ pub fn create_entry_block_alloca<'a, 'ctx>(
pub fn build_proc_header<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
symbol: Symbol,
layout: &Layout<'a>,
proc: &Proc<'a>,
) -> (FunctionValue<'ctx>, Vec<'a, BasicTypeEnum<'ctx>>) {
let args = proc.args;
@ -972,11 +971,12 @@ pub fn build_proc_header<'a, 'ctx, 'env>(
let fn_type = get_fn_type(&ret_type, &arg_basic_types);
let fn_val = env.module.add_function(
symbol.ident_string(&env.interns),
fn_type,
Some(Linkage::Private),
);
let fn_name = layout_ids
.get(symbol, layout)
.to_symbol_string(symbol, &env.interns);
let fn_val = env
.module
.add_function(fn_name.as_str(), fn_type, Some(Linkage::Private));
fn_val.set_call_conventions(fn_val.get_call_conventions());
@ -985,8 +985,8 @@ pub fn build_proc_header<'a, 'ctx, 'env>(
pub fn build_proc<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
proc: Proc<'a>,
procs: &Procs<'a>,
fn_val: FunctionValue<'ctx>,
arg_basic_types: Vec<'a, BasicTypeEnum<'ctx>>,
) {
@ -1015,7 +1015,7 @@ pub fn build_proc<'a, 'ctx, 'env>(
scope.insert(*arg_symbol, (layout.clone(), alloca));
}
let body = build_expr(env, &scope, fn_val, &proc.body, procs);
let body = build_expr(env, layout_ids, &scope, fn_val, &proc.body);
builder.build_return(Some(&body));
}
@ -1033,10 +1033,12 @@ pub fn verify_fn(fn_val: FunctionValue<'_>) {
#[inline(always)]
#[allow(clippy::cognitive_complexity)]
fn call_with_args<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
symbol: Symbol,
parent: FunctionValue<'ctx>,
args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)],
env: &Env<'a, 'ctx, 'env>,
) -> BasicValueEnum<'ctx> {
match symbol {
Symbol::INT_ADD | Symbol::NUM_ADD => {
@ -1385,9 +1387,12 @@ fn call_with_args<'a, 'ctx, 'env>(
BasicValueEnum::IntValue(int_val)
}
_ => {
let fn_name = layout_ids
.get(symbol, layout)
.to_symbol_string(symbol, &env.interns);
let fn_val = env
.module
.get_function(symbol.ident_string(&env.interns))
.get_function(fn_name.as_str())
.unwrap_or_else(|| panic!("Unrecognized function: {:?}", symbol));
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), env.arena);

View File

@ -38,7 +38,7 @@ macro_rules! assert_llvm_evals_to {
fpm.initialize();
// Compute main_fn_type before moving subs to Env
let layout = Layout::from_content(&arena, content, &subs, ptr_bytes)
let layout = Layout::new(&arena, content, &subs, ptr_bytes)
.unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs));
let execution_engine =
module
@ -60,6 +60,7 @@ macro_rules! assert_llvm_evals_to {
};
let mut procs = Procs::default();
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
let mut layout_ids = roc_gen::layout_id::LayoutIds::default();
// Populate Procs and get the low-level Expr from the canonical Expr
let mut mono_problems = Vec::new();
@ -78,13 +79,16 @@ macro_rules! assert_llvm_evals_to {
env.interns.all_ident_ids.insert(home, ident_ids);
let mut headers = Vec::with_capacity(procs.len());
let (mut proc_map, runtime_errors) = procs.into_map();
assert_eq!(runtime_errors, roc_collections::all::MutSet::default());
// Add all the Proc headers to the module.
// We have to do this in a separate pass first,
// because their bodies may reference each other.
for (symbol, opt_proc) in procs.as_map().into_iter() {
if let Some(proc) = opt_proc {
let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc);
for (symbol, mut procs_by_layout) in proc_map.drain() {
for (layout, proc) in procs_by_layout.drain() {
let (fn_val, arg_basic_types) = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
headers.push((proc, fn_val, arg_basic_types));
}
@ -96,7 +100,7 @@ macro_rules! assert_llvm_evals_to {
// (This approach means we don't have to defensively clone name here.)
//
// println!("\n\nBuilding and then verifying function {}\n\n", name);
build_proc(&env, proc, &procs, fn_val, arg_basic_types);
build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types);
if fn_val.verify(true) {
fpm.run_on(&fn_val);
@ -119,10 +123,10 @@ macro_rules! assert_llvm_evals_to {
let ret = roc_gen::llvm::build::build_expr(
&env,
&mut layout_ids,
&ImMap::default(),
main_fn,
&main_body,
&mut Procs::default(),
);
builder.build_return(Some(&ret));
@ -198,7 +202,7 @@ macro_rules! assert_opt_evals_to {
fpm.initialize();
// Compute main_fn_type before moving subs to Env
let layout = Layout::from_content(&arena, content, &subs, ptr_bytes)
let layout = Layout::new(&arena, content, &subs, ptr_bytes)
.unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs));
let execution_engine =
@ -221,6 +225,7 @@ macro_rules! assert_opt_evals_to {
};
let mut procs = Procs::default();
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
let mut layout_ids = roc_gen::layout_id::LayoutIds::default();
// Populate Procs and get the low-level Expr from the canonical Expr
let mut mono_problems = Vec::new();
@ -239,13 +244,16 @@ macro_rules! assert_opt_evals_to {
env.interns.all_ident_ids.insert(home, ident_ids);
let mut headers = Vec::with_capacity(procs.len());
let (mut proc_map, runtime_errors) = procs.into_map();
assert_eq!(runtime_errors, roc_collections::all::MutSet::default());
// Add all the Proc headers to the module.
// We have to do this in a separate pass first,
// because their bodies may reference each other.
for (symbol, opt_proc) in procs.as_map().into_iter() {
if let Some(proc) = opt_proc {
let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc);
for (symbol, mut procs_by_layout) in proc_map.drain() {
for (layout, proc) in procs_by_layout.drain() {
let (fn_val, arg_basic_types) = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
headers.push((proc, fn_val, arg_basic_types));
}
@ -257,7 +265,7 @@ macro_rules! assert_opt_evals_to {
// (This approach means we don't have to defensively clone name here.)
//
// println!("\n\nBuilding and then verifying function {}\n\n", name);
build_proc(&env, proc, &procs, fn_val, arg_basic_types);
build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types);
if fn_val.verify(true) {
fpm.run_on(&fn_val);
@ -280,10 +288,10 @@ macro_rules! assert_opt_evals_to {
let ret = roc_gen::llvm::build::build_expr(
&env,
&mut layout_ids,
&ImMap::default(),
main_fn,
&main_body,
&mut Procs::default(),
);
builder.build_return(Some(&ret));
@ -354,7 +362,7 @@ macro_rules! emit_expr {
fpm.initialize();
// Compute main_fn_type before moving subs to Env
let layout = Layout::from_content(&arena, content, &subs, ptr_bytes)
let layout = Layout::new(&arena, content, &subs, ptr_bytes)
.unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs));
let execution_engine =

View File

@ -48,6 +48,10 @@ impl Symbol {
IdentId((self.0 >> 32) as u32)
}
pub fn is_builtin(self) -> bool {
self.module_id().is_builtin()
}
pub fn module_string<'a>(&self, interns: &'a Interns) -> &'a InlinableString {
interns
.module_ids

View File

@ -1203,14 +1203,16 @@ fn boolean_all<'a>(arena: &'a Bump, tests: Vec<(Expr<'a>, Expr<'a>, Layout<'a>)>
let mut expr = Expr::Bool(true);
for (lhs, rhs, layout) in tests.into_iter().rev() {
let test = specialize_equality(arena, lhs, rhs, layout);
expr = Expr::CallByName(
Symbol::BOOL_AND,
arena.alloc([
let test = specialize_equality(arena, lhs, rhs, layout.clone());
expr = Expr::CallByName {
name: Symbol::BOOL_AND,
layout,
args: arena.alloc([
(test, Layout::Builtin(Builtin::Bool)),
(expr, Layout::Builtin(Builtin::Bool)),
]),
);
};
}
expr

View File

@ -1,12 +1,13 @@
use crate::layout::{Builtin, Layout};
use crate::layout::{Builtin, Layout, LayoutCache};
use crate::pattern::{Ctor, Guard, RenderAs, TagId};
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_collections::all::{MutMap, MutSet};
use roc_collections::all::{default_hasher, MutMap, MutSet};
use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_region::all::{Located, Region};
use roc_types::subs::{Content, ContentHash, FlatType, Subs, Variable};
use roc_types::subs::{Content, FlatType, Subs, Variable};
use std::collections::HashMap;
use std::hash::Hash;
#[derive(Clone, Debug, PartialEq)]
@ -29,8 +30,9 @@ pub struct Proc<'a> {
pub struct Procs<'a> {
pub user_defined: MutMap<Symbol, PartialProc<'a>>,
pub module_thunks: MutSet<Symbol>,
anonymous: MutMap<Symbol, Option<Proc<'a>>>,
specializations: MutMap<ContentHash, (Symbol, Option<Proc<'a>>)>,
runtime_errors: MutSet<Symbol>,
specializations: MutMap<Symbol, MutMap<Layout<'a>, Proc<'a>>>,
pending_specializations: MutMap<Symbol, Layout<'a>>,
builtin: MutSet<Symbol>,
}
@ -57,6 +59,8 @@ impl<'a> Procs<'a> {
);
}
// TODO trim these down
#[allow(clippy::too_many_arguments)]
pub fn insert_anonymous(
&mut self,
env: &mut Env<'a, '_>,
@ -65,13 +69,14 @@ impl<'a> Procs<'a> {
loc_args: std::vec::Vec<(Variable, Located<roc_can::pattern::Pattern>)>,
loc_body: Located<roc_can::expr::Expr>,
ret_var: Variable,
) {
layout_cache: &mut LayoutCache<'a>,
) -> Result<Layout<'a>, ()> {
let (arg_vars, arg_symbols, body) = patterns_to_when(env, loc_args, ret_var, loc_body);
// an anonymous closure. These will always be specialized already
// by the surrounding context
let opt_proc = specialize_proc_body(
match specialize_proc_body(
env,
self,
annotation,
@ -81,19 +86,38 @@ impl<'a> Procs<'a> {
&arg_symbols,
annotation,
body.value,
)
.ok();
layout_cache,
) {
Ok(proc) => {
let layout = layout_cache
.from_var(env.arena, annotation, env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err));
self.anonymous.insert(symbol, opt_proc);
self.insert_specialization(symbol, layout.clone(), proc);
Ok(layout)
}
Err(()) => {
self.runtime_errors.insert(symbol);
Err(())
}
}
}
fn insert_specialization(
&mut self,
hash: ContentHash,
spec_name: Symbol,
proc: Option<Proc<'a>>,
) {
self.specializations.insert(hash, (spec_name, proc));
fn insert_specialization(&mut self, symbol: Symbol, layout: Layout<'a>, proc: Proc<'a>) {
let procs_by_layout = self
.specializations
.entry(symbol)
.or_insert_with(|| HashMap::with_capacity_and_hasher(1, default_hasher()));
// If we already have an entry for this, it should be no different
// from what we're about to insert.
debug_assert!(
!procs_by_layout.contains_key(&layout) || procs_by_layout.get(&layout) == Some(&proc)
);
procs_by_layout.insert(layout, proc);
}
fn get_user_defined(&self, symbol: Symbol) -> Option<&PartialProc<'a>> {
@ -101,10 +125,10 @@ impl<'a> Procs<'a> {
}
pub fn len(&self) -> usize {
let anonymous: usize = self.anonymous.len();
let user_defined: usize = self.specializations.len();
let runtime_errors: usize = self.runtime_errors.len();
let specializations: usize = self.specializations.len();
anonymous + user_defined
runtime_errors + specializations
}
pub fn is_empty(&self) -> bool {
@ -115,22 +139,20 @@ impl<'a> Procs<'a> {
self.builtin.insert(symbol);
}
pub fn as_map(&self) -> MutMap<Symbol, Option<Proc<'a>>> {
let mut result = MutMap::default();
for (symbol, opt_proc) in self.specializations.values() {
result.insert(*symbol, opt_proc.clone());
}
for (symbol, proc) in self.anonymous.clone().into_iter() {
result.insert(symbol, proc);
}
pub fn into_map(self) -> (MutMap<Symbol, MutMap<Layout<'a>, Proc<'a>>>, MutSet<Symbol>) {
let mut specializations = self.specializations;
for symbol in self.builtin.iter() {
result.insert(*symbol, None);
// Builtins should only ever be stored as empty maps.
debug_assert!(
!specializations.contains_key(&symbol)
|| specializations.get(&symbol).unwrap().is_empty()
);
specializations.insert(*symbol, MutMap::default());
}
result
(specializations, self.runtime_errors)
}
}
@ -177,8 +199,13 @@ pub enum Expr<'a> {
Store(&'a [(Symbol, Layout<'a>, Expr<'a>)], &'a Expr<'a>),
// Functions
FunctionPointer(Symbol),
CallByName(Symbol, &'a [(Expr<'a>, Layout<'a>)]),
FunctionPointer(Symbol, Layout<'a>),
RuntimeErrorFunction(&'a str),
CallByName {
name: Symbol,
layout: Layout<'a>,
args: &'a [(Expr<'a>, Layout<'a>)],
},
CallByPointer(&'a Expr<'a>, &'a [Expr<'a>], Layout<'a>),
// Exactly two conditional branches, e.g. if/else
@ -248,7 +275,9 @@ impl<'a> Expr<'a> {
can_expr: roc_can::expr::Expr,
procs: &mut Procs<'a>,
) -> Self {
from_can(env, can_expr, procs, None)
let mut layout_cache = LayoutCache::default();
from_can(env, can_expr, procs, &mut layout_cache)
}
}
@ -448,7 +477,7 @@ fn from_can<'a>(
env: &mut Env<'a, '_>,
can_expr: roc_can::expr::Expr,
procs: &mut Procs<'a>,
name: Option<Symbol>,
layout_cache: &mut LayoutCache<'a>,
) -> Expr<'a> {
use roc_can::expr::Expr::*;
@ -467,123 +496,51 @@ fn from_can<'a>(
let ret_var = partial_proc.annotation;
// This is a top-level declaration, which will code gen to a 0-arity thunk.
call_by_name(env, procs, fn_var, ret_var, symbol, std::vec::Vec::new())
call_by_name(
env,
procs,
fn_var,
ret_var,
symbol,
std::vec::Vec::new(),
layout_cache,
)
} else {
Expr::Load(symbol)
}
}
LetRec(defs, ret_expr, _, _) => from_can_defs(env, defs, *ret_expr, procs),
LetNonRec(def, ret_expr, _, _) => from_can_defs(env, vec![*def], *ret_expr, procs),
LetRec(defs, ret_expr, _, _) => from_can_defs(env, defs, *ret_expr, layout_cache, procs),
LetNonRec(def, ret_expr, _, _) => {
from_can_defs(env, vec![*def], *ret_expr, layout_cache, procs)
}
Closure(ann, original_name, _, loc_args, boxed_body) => {
Closure(ann, name, _, loc_args, boxed_body) => {
let (loc_body, ret_var) = *boxed_body;
let symbol = match name {
Some(symbol) => {
procs.insert_named(env, symbol, ann, loc_args, loc_body, ret_var);
symbol
match procs.insert_anonymous(env, name, ann, loc_args, loc_body, ret_var, layout_cache)
{
Ok(layout) => Expr::FunctionPointer(name, layout),
Err(()) => {
// TODO make this message better
Expr::RuntimeErrorFunction("This function threw a runtime error.")
}
None => {
procs.insert_anonymous(env, original_name, ann, loc_args, loc_body, ret_var);
original_name
}
};
Expr::FunctionPointer(symbol)
}
}
Call(boxed, loc_args, _) => {
use IntOrFloat::*;
let (fn_var, loc_expr, ret_var) = *boxed;
let specialize_builtin_functions = {
|env: &mut Env<'a, '_>, symbol: Symbol| {
if !symbol.module_id().is_builtin() {
// return unchanged
symbol
} else {
match symbol {
Symbol::NUM_ADD => match num_to_int_or_float(env.subs, ret_var) {
FloatType => Symbol::FLOAT_ADD,
IntType => Symbol::INT_ADD,
},
Symbol::NUM_SUB => match num_to_int_or_float(env.subs, ret_var) {
FloatType => Symbol::FLOAT_SUB,
IntType => Symbol::INT_SUB,
},
Symbol::NUM_LTE => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_LTE,
IntType => Symbol::INT_LTE,
},
Symbol::NUM_LT => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_LT,
IntType => Symbol::INT_LT,
},
Symbol::NUM_GTE => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_GTE,
IntType => Symbol::INT_GTE,
},
Symbol::NUM_GT => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_GT,
IntType => Symbol::INT_GT,
},
// TODO make this work for more than just int/float
Symbol::BOOL_EQ => {
match Layout::from_var(
env.arena,
loc_args[0].0,
env.subs,
env.pointer_size,
) {
Ok(Layout::Builtin(builtin)) => match builtin {
Builtin::Int64 => Symbol::INT_EQ_I64,
Builtin::Float64 => Symbol::FLOAT_EQ,
Builtin::Bool => Symbol::INT_EQ_I1,
Builtin::Byte => Symbol::INT_EQ_I8,
_ => panic!("Equality not implemented for {:?}", builtin),
},
Ok(complex) => panic!(
"TODO support equality on complex layouts like {:?}",
complex
),
Err(()) => panic!("Invalid layout"),
}
}
Symbol::BOOL_NEQ => {
match Layout::from_var(
env.arena,
loc_args[0].0,
env.subs,
env.pointer_size,
) {
Ok(Layout::Builtin(builtin)) => match builtin {
Builtin::Int64 => Symbol::INT_NEQ_I64,
Builtin::Bool => Symbol::INT_NEQ_I1,
Builtin::Byte => Symbol::INT_NEQ_I8,
_ => {
panic!("Not-Equality not implemented for {:?}", builtin)
}
},
Ok(complex) => panic!(
"TODO support equality on complex layouts like {:?}",
complex
),
Err(()) => panic!("Invalid layout"),
}
}
_ => symbol,
}
}
}
};
match from_can(env, loc_expr.value, procs, None) {
match from_can(env, loc_expr.value, procs, layout_cache) {
Expr::Load(proc_name) => {
// Some functions can potentially mutate in-place.
// If we have one of those, switch to the in-place version if appropriate.
match specialize_builtin_functions(env, proc_name) {
match specialize_builtin_functions(
env,
proc_name,
loc_args.as_slice(),
ret_var,
layout_cache,
) {
Symbol::LIST_SET => {
let subs = &env.subs;
// The first arg is the one with the List in it.
@ -610,9 +567,25 @@ fn from_can<'a>(
Symbol::LIST_SET
};
call_by_name(env, procs, fn_var, ret_var, new_name, loc_args)
call_by_name(
env,
procs,
fn_var,
ret_var,
new_name,
loc_args,
layout_cache,
)
}
_ => call_by_name(env, procs, fn_var, ret_var, proc_name, loc_args),
_ => call_by_name(
env,
procs,
fn_var,
ret_var,
proc_name,
loc_args,
layout_cache,
),
}
}
specialized_proc_symbol => call_by_name(
@ -622,6 +595,7 @@ fn from_can<'a>(
ret_var,
specialized_proc_symbol,
loc_args,
layout_cache,
),
}
}
@ -640,10 +614,11 @@ fn from_can<'a>(
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));
args.push(from_can(env, loc_arg.value, procs, layout_cache));
}
let layout = Layout::from_var(env.arena, fn_var, env.subs, env.pointer_size)
let layout = layout_cache
.from_var(env.arena, fn_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| {
panic!("TODO turn fn_var into a RuntimeError {:?}", err)
});
@ -658,7 +633,16 @@ fn from_can<'a>(
region,
loc_cond,
branches,
} => from_can_when(env, cond_var, expr_var, region, *loc_cond, branches, procs),
} => from_can_when(
env,
cond_var,
expr_var,
region,
*loc_cond,
branches,
layout_cache,
procs,
),
If {
cond_var,
@ -666,16 +650,18 @@ fn from_can<'a>(
branches,
final_else,
} => {
let mut expr = from_can(env, final_else.value, procs, None);
let mut expr = from_can(env, final_else.value, procs, layout_cache);
let ret_layout = Layout::from_var(env.arena, branch_var, env.subs, env.pointer_size)
let ret_layout = layout_cache
.from_var(env.arena, branch_var, env.subs, env.pointer_size)
.expect("invalid ret_layout");
let cond_layout = Layout::from_var(env.arena, cond_var, env.subs, env.pointer_size)
let cond_layout = layout_cache
.from_var(env.arena, cond_var, env.subs, env.pointer_size)
.expect("invalid cond_layout");
for (loc_cond, loc_then) in branches.into_iter().rev() {
let cond = from_can(env, loc_cond.value, procs, None);
let then = from_can(env, loc_then.value, procs, None);
let cond = from_can(env, loc_cond.value, procs, layout_cache);
let then = from_can(env, loc_then.value, procs, layout_cache);
let branch_symbol = env.unique_symbol();
@ -716,7 +702,7 @@ fn from_can<'a>(
for (label, layout) in btree {
let field = fields.remove(&label).unwrap();
let expr = from_can(env, field.loc_expr.value, procs, None);
let expr = from_can(env, field.loc_expr.value, procs, layout_cache);
field_tuples.push((expr, layout));
}
@ -757,7 +743,7 @@ fn from_can<'a>(
Unwrapped(field_layouts) => {
let field_exprs = args
.into_iter()
.map(|(_, arg)| from_can(env, arg.value, procs, None));
.map(|(_, arg)| from_can(env, arg.value, procs, layout_cache));
let mut field_tuples = Vec::with_capacity_in(field_layouts.len(), arena);
@ -779,7 +765,7 @@ fn from_can<'a>(
let it = std::iter::once(Expr::Int(tag_id as i64)).chain(
args.into_iter()
.map(|(_, arg)| from_can(env, arg.value, procs, None)),
.map(|(_, arg)| from_can(env, arg.value, procs, layout_cache)),
);
for (arg_layout, arg_expr) in argument_layouts.iter().zip(it) {
@ -832,7 +818,7 @@ fn from_can<'a>(
}
}
let record = arena.alloc(from_can(env, loc_expr.value, procs, None));
let record = arena.alloc(from_can(env, loc_expr.value, procs, layout_cache));
Expr::AccessAtIndex {
index: index.expect("field not in its own type") as u64,
@ -853,8 +839,8 @@ fn from_can<'a>(
// We have to special-case the empty list, because trying to
// compute a layout for an unbound var won't work.
Content::FlexVar(_) => Layout::Builtin(Builtin::EmptyList),
content => match Layout::from_content(arena, content, env.subs, env.pointer_size) {
Ok(layout) => layout,
_ => match layout_cache.from_var(arena, elem_var, env.subs, env.pointer_size) {
Ok(layout) => layout.clone(),
Err(()) => {
panic!("TODO gracefully handle List with invalid element layout");
}
@ -864,7 +850,7 @@ fn from_can<'a>(
let mut elems = Vec::with_capacity_in(loc_elems.len(), arena);
for loc_elem in loc_elems {
elems.push(from_can(env, loc_elem.value, procs, None));
elems.push(from_can(env, loc_elem.value, procs, layout_cache));
}
Expr::Array {
@ -1022,6 +1008,7 @@ fn from_can_defs<'a>(
env: &mut Env<'a, '_>,
defs: std::vec::Vec<roc_can::def::Def>,
ret_expr: Located<roc_can::expr::Expr>,
layout_cache: &mut LayoutCache<'a>,
procs: &mut Procs<'a>,
) -> Expr<'a> {
use roc_can::expr::Expr::*;
@ -1029,6 +1016,7 @@ fn from_can_defs<'a>(
let arena = env.arena;
let mut stored = Vec::with_capacity_in(defs.len(), arena);
for def in defs {
let loc_pattern = def.loc_pattern;
let loc_expr = def.loc_expr;
@ -1047,18 +1035,29 @@ fn from_can_defs<'a>(
//
if let Identifier(symbol) = &loc_pattern.value {
if let Closure(_, _, _, _, _) = &loc_expr.value {
// 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(*symbol));
// Now that we know for sure it's a closure, get an owned
// version of these variant args so we can use them properly.
match loc_expr.value {
Closure(ann, _, _, loc_args, boxed_body) => {
// Extract Procs, but discard the resulting Expr::Load.
// That Load looks up the pointer, which we won't use here!
continue;
let (loc_body, ret_var) = *boxed_body;
procs.insert_named(env, *symbol, ann, loc_args, loc_body, ret_var);
continue;
}
_ => unreachable!(),
}
}
}
// If it wasn't specifically an Identifier & Closure, proceed as normal.
let mono_pattern = from_can_pattern(env, &loc_pattern.value);
let layout = Layout::from_var(env.arena, def.expr_var, env.subs, env.pointer_size)
let layout = layout_cache
.from_var(env.arena, def.expr_var, env.subs, env.pointer_size)
.expect("invalid layout");
match &mono_pattern {
@ -1066,7 +1065,7 @@ fn from_can_defs<'a>(
stored.push((
*symbol,
layout.clone(),
from_can(env, loc_expr.value, procs, None),
from_can(env, loc_expr.value, procs, layout_cache),
));
}
_ => {
@ -1093,7 +1092,7 @@ fn from_can_defs<'a>(
stored.push((
symbol,
layout.clone(),
from_can(env, loc_expr.value, procs, None),
from_can(env, loc_expr.value, procs, layout_cache),
));
match store_pattern(env, &mono_pattern, symbol, layout, &mut stored) {
@ -1108,7 +1107,7 @@ fn from_can_defs<'a>(
}
// 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);
let ret = from_can(env, ret_expr.value, procs, layout_cache);
if stored.is_empty() {
ret
@ -1117,6 +1116,8 @@ fn from_can_defs<'a>(
}
}
// TODO trim these down
#[allow(clippy::too_many_arguments)]
fn from_can_when<'a>(
env: &mut Env<'a, '_>,
cond_var: Variable,
@ -1124,6 +1125,7 @@ fn from_can_when<'a>(
region: Region,
loc_cond: Located<roc_can::expr::Expr>,
mut branches: std::vec::Vec<roc_can::expr::WhenBranch>,
layout_cache: &mut LayoutCache<'a>,
procs: &mut Procs<'a>,
) -> Expr<'a> {
if branches.is_empty() {
@ -1168,32 +1170,34 @@ fn from_can_when<'a>(
}
}
let cond_layout = Layout::from_var(env.arena, cond_var, env.subs, env.pointer_size)
let cond_layout = layout_cache
.from_var(env.arena, cond_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err));
let cond_symbol = env.unique_symbol();
let cond = from_can(env, loc_cond.value, procs, None);
let cond = from_can(env, loc_cond.value, procs, layout_cache);
stored.push((cond_symbol, cond_layout.clone(), cond));
// NOTE this will still store shadowed names.
// that's fine: the branch throws a runtime error anyway
let ret = match store_pattern(env, &mono_pattern, cond_symbol, cond_layout, &mut stored) {
Ok(_) => from_can(env, first.value.value, procs, None),
Ok(_) => from_can(env, first.value.value, procs, layout_cache),
Err(message) => Expr::RuntimeError(env.arena.alloc(message)),
};
Expr::Store(stored.into_bump_slice(), arena.alloc(ret))
} else {
let cond_layout = Layout::from_var(env.arena, cond_var, env.subs, env.pointer_size)
let cond_layout = layout_cache
.from_var(env.arena, cond_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err));
let cond = from_can(env, loc_cond.value, procs, None);
let cond = from_can(env, loc_cond.value, procs, layout_cache);
let cond_symbol = env.unique_symbol();
let mut loc_branches = std::vec::Vec::new();
let mut opt_branches = std::vec::Vec::new();
for when_branch in branches {
let mono_expr = from_can(env, when_branch.value.value, procs, None);
let mono_expr = from_can(env, when_branch.value.value, procs, layout_cache);
let exhaustive_guard = if when_branch.guard.is_some() {
Guard::HasGuard
@ -1226,7 +1230,7 @@ fn from_can_when<'a>(
//
// otherwise, we modify the branch's expression to include the stores
if let Some(loc_guard) = when_branch.guard.clone() {
let expr = from_can(env, loc_guard.value, procs, None);
let expr = from_can(env, loc_guard.value, procs, layout_cache);
(
crate::decision_tree::Guard::Guard {
stores: stores.into_bump_slice(),
@ -1306,7 +1310,8 @@ fn from_can_when<'a>(
}
}
let ret_layout = Layout::from_var(env.arena, expr_var, env.subs, env.pointer_size)
let ret_layout = layout_cache
.from_var(env.arena, expr_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err));
let branching = crate::decision_tree::optimize_when(
@ -1330,6 +1335,7 @@ fn call_by_name<'a>(
ret_var: Variable,
proc_name: Symbol,
loc_args: std::vec::Vec<(Variable, Located<roc_can::expr::Expr>)>,
layout_cache: &mut LayoutCache<'a>,
) -> Expr<'a> {
// create specialized procedure to call
@ -1339,79 +1345,96 @@ fn call_by_name<'a>(
// get a borrow checker error about trying to borrow `procs` as mutable
// while there is still an active immutable borrow.
#[allow(clippy::type_complexity)]
let opt_specialize_body: Option<(
ContentHash,
Variable,
roc_can::expr::Expr,
Vec<'a, Symbol>,
)>;
let opt_specialize_body: Option<(Variable, roc_can::expr::Expr, Vec<'a, Symbol>)>;
let specialized_proc_name = match procs.get_user_defined(proc_name) {
Some(partial_proc) => {
let content_hash = ContentHash::from_var(fn_var, env.subs);
match procs.specializations.get(&content_hash) {
Some(specialization) => {
opt_specialize_body = None;
// a specialization with this type hash already exists, so use its symbol
specialization.0
match layout_cache.from_var(env.arena, fn_var, env.subs, env.pointer_size) {
Ok(layout) => {
match procs.get_user_defined(proc_name) {
Some(partial_proc) => {
match procs
.specializations
.get(&proc_name)
.and_then(|procs_by_layout| procs_by_layout.get(&layout))
{
Some(_) => {
// a specialization with this layout already exists.
opt_specialize_body = None;
}
None => {
if procs.pending_specializations.get(&proc_name) == Some(&layout) {
// If we're already in the process of specializing this, don't
// try to specialize it further; otherwise, we'll loop forever.
opt_specialize_body = None;
} else {
opt_specialize_body = Some((
partial_proc.annotation,
partial_proc.body.clone(),
partial_proc.patterns.clone(),
));
}
}
}
}
None => {
opt_specialize_body = Some((
content_hash,
partial_proc.annotation,
partial_proc.body.clone(),
partial_proc.patterns.clone(),
));
opt_specialize_body = None;
// generate a symbol for this specialization
env.unique_symbol()
// This happens for built-in symbols (they are never defined as a Closure)
procs.insert_builtin(proc_name);
}
};
if let Some((annotation, body, loc_patterns)) = opt_specialize_body {
// register proc, so specialization doesn't loop infinitely
procs
.pending_specializations
.insert(proc_name, layout.clone());
let arg_vars = loc_args.iter().map(|v| v.0).collect::<std::vec::Vec<_>>();
match specialize_proc_body(
env,
procs,
fn_var,
ret_var,
proc_name,
&arg_vars,
&loc_patterns,
annotation,
body,
layout_cache,
) {
Ok(proc) => {
procs.insert_specialization(proc_name, layout.clone(), proc);
}
Err(()) => {
procs.runtime_errors.insert(proc_name);
}
}
}
// generate actual call
let mut args = Vec::with_capacity_in(loc_args.len(), env.arena);
for (var, loc_arg) in loc_args {
let layout = layout_cache
.from_var(&env.arena, var, &env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO gracefully handle bad layout: {:?}", err));
args.push((from_can(env, loc_arg.value, procs, layout_cache), layout));
}
Expr::CallByName {
name: proc_name,
layout,
args: args.into_bump_slice(),
}
}
None => {
opt_specialize_body = None;
// This happens for built-in symbols (they are never defined as a Closure)
procs.insert_builtin(proc_name);
proc_name
Err(()) => {
// This function code gens to a runtime error,
// so attempting to call it will immediately crash.
Expr::RuntimeError("")
}
};
if let Some((content_hash, annotation, body, loc_patterns)) = opt_specialize_body {
// register proc, so specialization doesn't loop infinitely
procs.insert_specialization(content_hash, specialized_proc_name, None);
let arg_vars = loc_args.iter().map(|v| v.0).collect::<std::vec::Vec<_>>();
let proc = specialize_proc_body(
env,
procs,
fn_var,
ret_var,
specialized_proc_name,
&arg_vars,
&loc_patterns,
annotation,
body,
)
.ok();
procs.insert_specialization(content_hash, specialized_proc_name, proc);
}
// generate actual call
let mut args = Vec::with_capacity_in(loc_args.len(), env.arena);
for (var, loc_arg) in loc_args {
let layout = Layout::from_var(&env.arena, var, &env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO gracefully handle bad layout: {:?}", err));
args.push((from_can(env, loc_arg.value, procs, None), layout));
}
Expr::CallByName(specialized_proc_name, args.into_bump_slice())
}
#[allow(clippy::too_many_arguments)]
@ -1425,23 +1448,26 @@ fn specialize_proc_body<'a>(
pattern_symbols: &[Symbol],
annotation: Variable,
body: roc_can::expr::Expr,
layout_cache: &mut LayoutCache<'a>,
) -> Result<Proc<'a>, ()> {
// unify the called function with the specialized signature, then specialize the function body
let snapshot = env.subs.snapshot();
let unified = roc_unify::unify::unify(env.subs, annotation, fn_var);
debug_assert!(matches!(unified, roc_unify::unify::Unified::Success(_)));
let specialized_body = from_can(env, body, procs, None);
let specialized_body = from_can(env, body, procs, layout_cache);
// reset subs, so we don't get type errors when specializing for a different signature
env.subs.rollback_to(snapshot);
let mut proc_args = Vec::with_capacity_in(loc_args.len(), &env.arena);
for (arg_var, arg_name) in loc_args.iter().zip(pattern_symbols.iter()) {
let layout = Layout::from_var(&env.arena, *arg_var, env.subs, env.pointer_size)?;
let layout = layout_cache.from_var(&env.arena, *arg_var, env.subs, env.pointer_size)?;
proc_args.push((layout, *arg_name));
}
let ret_layout = Layout::from_var(&env.arena, ret_var, env.subs, env.pointer_size)
let ret_layout = layout_cache
.from_var(&env.arena, ret_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err));
let proc = Proc {
@ -1752,7 +1778,7 @@ pub fn specialize_equality<'a>(
rhs: Expr<'a>,
layout: Layout<'a>,
) -> Expr<'a> {
let symbol = match &layout {
let name = match &layout {
Layout::Builtin(builtin) => match builtin {
Builtin::Int64 => Symbol::INT_EQ_I64,
Builtin::Float64 => Symbol::FLOAT_EQ,
@ -1763,8 +1789,84 @@ pub fn specialize_equality<'a>(
other => todo!("Cannot yet compare for equality {:?}", other),
};
Expr::CallByName(
symbol,
arena.alloc([(lhs, layout.clone()), (rhs, layout.clone())]),
)
Expr::CallByName {
name,
layout: layout.clone(),
args: arena.alloc([(lhs, layout.clone()), (rhs, layout)]),
}
}
fn specialize_builtin_functions<'a>(
env: &mut Env<'a, '_>,
symbol: Symbol,
loc_args: &[(Variable, Located<roc_can::expr::Expr>)],
ret_var: Variable,
layout_cache: &mut LayoutCache<'a>,
) -> Symbol {
use IntOrFloat::*;
if !symbol.is_builtin() {
// return unchanged
symbol
} else {
match symbol {
Symbol::NUM_ADD => match num_to_int_or_float(env.subs, ret_var) {
FloatType => Symbol::FLOAT_ADD,
IntType => Symbol::INT_ADD,
},
Symbol::NUM_SUB => match num_to_int_or_float(env.subs, ret_var) {
FloatType => Symbol::FLOAT_SUB,
IntType => Symbol::INT_SUB,
},
Symbol::NUM_LTE => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_LTE,
IntType => Symbol::INT_LTE,
},
Symbol::NUM_LT => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_LT,
IntType => Symbol::INT_LT,
},
Symbol::NUM_GTE => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_GTE,
IntType => Symbol::INT_GTE,
},
Symbol::NUM_GT => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_GT,
IntType => Symbol::INT_GT,
},
// TODO make this work for more than just int/float
Symbol::BOOL_EQ => {
match layout_cache.from_var(env.arena, loc_args[0].0, env.subs, env.pointer_size) {
Ok(Layout::Builtin(builtin)) => match builtin {
Builtin::Int64 => Symbol::INT_EQ_I64,
Builtin::Float64 => Symbol::FLOAT_EQ,
Builtin::Bool => Symbol::INT_EQ_I1,
Builtin::Byte => Symbol::INT_EQ_I8,
_ => panic!("Equality not implemented for {:?}", builtin),
},
Ok(complex) => panic!(
"TODO support equality on complex layouts like {:?}",
complex
),
Err(()) => panic!("Invalid layout"),
}
}
Symbol::BOOL_NEQ => {
match layout_cache.from_var(env.arena, loc_args[0].0, env.subs, env.pointer_size) {
Ok(Layout::Builtin(builtin)) => match builtin {
Builtin::Int64 => Symbol::INT_NEQ_I64,
Builtin::Bool => Symbol::INT_NEQ_I1,
Builtin::Byte => Symbol::INT_NEQ_I8,
_ => panic!("Not-Equality not implemented for {:?}", builtin),
},
Ok(complex) => panic!(
"TODO support equality on complex layouts like {:?}",
complex
),
Err(()) => panic!("Invalid layout"),
}
}
_ => symbol,
}
}
}

View File

@ -36,21 +36,7 @@ pub enum Builtin<'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_var(
arena: &'a Bump,
var: Variable,
subs: &Subs,
pointer_size: u32,
) -> Result<Self, ()> {
let content = subs.get_without_compacting(var).content;
Self::from_content(arena, content, subs, pointer_size)
}
pub fn from_content(
pub fn new(
arena: &'a Bump,
content: Content,
subs: &Subs,
@ -61,7 +47,7 @@ impl<'a> Layout<'a> {
match content {
var @ FlexVar(_) | var @ RigidVar(_) => {
panic!(
"Layout::from_content encountered an unresolved {:?} - subs was {:?}",
"Layout::new encountered an unresolved {:?} - subs was {:?}",
var, subs
);
}
@ -75,7 +61,7 @@ impl<'a> Layout<'a> {
debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Float64))
}
Alias(_, _, var) => Self::from_content(
Alias(_, _, var) => Self::new(
arena,
subs.get_without_compacting(var).content,
subs,
@ -85,6 +71,20 @@ 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!
fn from_var(
arena: &'a Bump,
var: Variable,
subs: &Subs,
pointer_size: u32,
) -> Result<Self, ()> {
let content = subs.get_without_compacting(var).content;
Self::new(arena, content, subs, pointer_size)
}
pub fn safe_to_memcpy(&self) -> bool {
use Layout::*;
@ -137,6 +137,37 @@ impl<'a> Layout<'a> {
}
}
/// Avoid recomputing Layout from Variable multiple times.
#[derive(Default)]
pub struct LayoutCache<'a> {
layouts: MutMap<Variable, Result<Layout<'a>, ()>>,
}
impl<'a> LayoutCache<'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_var(
&mut self,
arena: &'a Bump,
var: Variable,
subs: &Subs,
pointer_size: u32,
) -> Result<Layout<'a>, ()> {
// Store things according to the root Variable, to avoid duplicate work.
let var = subs.get_root_key_without_compacting(var);
self.layouts
.entry(var)
.or_insert_with(|| {
let content = subs.get_without_compacting(var).content;
Layout::new(arena, content, subs, pointer_size)
})
.clone()
}
}
impl<'a> Builtin<'a> {
const I64_SIZE: u32 = std::mem::size_of::<i64>() as u32;
const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32;
@ -216,8 +247,7 @@ fn layout_from_flat_type<'a>(
match subs.get_without_compacting(args[0]).content {
FlexVar(_) | RigidVar(_) => Ok(Layout::Builtin(Builtin::EmptyList)),
content => {
let elem_layout =
Layout::from_content(arena, content, subs, pointer_size)?;
let elem_layout = Layout::new(arena, content, subs, pointer_size)?;
Ok(Layout::Builtin(Builtin::List(arena.alloc(elem_layout))))
}
@ -246,16 +276,11 @@ fn layout_from_flat_type<'a>(
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,
pointer_size,
)?);
fn_args.push(Layout::new(arena, arg_content, subs, pointer_size)?);
}
let ret_content = subs.get_without_compacting(ret_var).content;
let ret = Layout::from_content(arena, ret_content, subs, pointer_size)?;
let ret = Layout::new(arena, ret_content, subs, pointer_size)?;
Ok(Layout::FunctionPointer(
fn_args.into_bump_slice(),
@ -273,14 +298,13 @@ fn layout_from_flat_type<'a>(
for (_, field_var) in btree {
let field_content = subs.get_without_compacting(field_var).content;
let field_layout =
match Layout::from_content(arena, field_content, subs, pointer_size) {
Ok(layout) => layout,
Err(()) => {
// Invalid field!
panic!("TODO gracefully handle record with invalid field.var");
}
};
let field_layout = match Layout::new(arena, field_content, subs, pointer_size) {
Ok(layout) => layout,
Err(()) => {
// Invalid field!
panic!("TODO gracefully handle record with invalid field.var");
}
};
layouts.push(field_layout);
}

View File

@ -10,6 +10,7 @@
// and encouraging shortcuts here creates bad incentives. I would rather temporarily
// re-enable this when working on performance optimizations than have it block PRs.
#![allow(clippy::large_enum_variant)]
pub mod expr;
pub mod layout;

View File

@ -67,7 +67,7 @@ mod test_mono {
// Put this module's ident_ids back in the interns
interns.all_ident_ids.insert(home, ident_ids);
assert_eq!(mono_expr, get_expected(interns));
assert_eq!(get_expected(interns), mono_expr);
}
#[test]
@ -84,13 +84,20 @@ mod test_mono {
fn float_addition() {
compiles_to(
"3.0 + 4",
CallByName(
Symbol::FLOAT_ADD,
&[
CallByName {
name: Symbol::FLOAT_ADD,
layout: Layout::FunctionPointer(
&[
Layout::Builtin(Builtin::Float64),
Layout::Builtin(Builtin::Float64),
],
&Layout::Builtin(Builtin::Float64),
),
args: &[
(Float(3.0), Layout::Builtin(Builtin::Float64)),
(Float(4.0), Layout::Builtin(Builtin::Float64)),
],
),
},
);
}
@ -98,13 +105,20 @@ mod test_mono {
fn int_addition() {
compiles_to(
"0xDEADBEEF + 4",
CallByName(
Symbol::INT_ADD,
&[
CallByName {
name: Symbol::INT_ADD,
layout: Layout::FunctionPointer(
&[
Layout::Builtin(Builtin::Int64),
Layout::Builtin(Builtin::Int64),
],
&Layout::Builtin(Builtin::Int64),
),
args: &[
(Int(3735928559), Layout::Builtin(Builtin::Int64)),
(Int(4), Layout::Builtin(Builtin::Int64)),
],
),
},
);
}
@ -113,13 +127,20 @@ mod test_mono {
// Default to Int for `Num *`
compiles_to(
"3 + 5",
CallByName(
Symbol::INT_ADD,
&[
CallByName {
name: Symbol::INT_ADD,
layout: Layout::FunctionPointer(
&[
Layout::Builtin(Builtin::Int64),
Layout::Builtin(Builtin::Int64),
],
&Layout::Builtin(Builtin::Int64),
),
args: &[
(Int(3), Layout::Builtin(Builtin::Int64)),
(Int(5), Layout::Builtin(Builtin::Int64)),
],
),
},
);
}
@ -133,20 +154,31 @@ mod test_mono {
"#,
{
use self::Builtin::*;
use Layout::Builtin;
let home = test_home();
let gen_symbol_3 = Interns::from_index(home, 3);
let gen_symbol_4 = Interns::from_index(home, 4);
let gen_symbol_0 = Interns::from_index(home, 0);
Struct(&[
(
CallByName(gen_symbol_3, &[(Int(4), Builtin(Int64))]),
Builtin(Int64),
CallByName {
name: gen_symbol_0,
layout: Layout::FunctionPointer(
&[Layout::Builtin(Builtin::Int64)],
&Layout::Builtin(Builtin::Int64),
),
args: &[(Int(4), Layout::Builtin(Int64))],
},
Layout::Builtin(Int64),
),
(
CallByName(gen_symbol_4, &[(Float(3.14), Builtin(Float64))]),
Builtin(Float64),
CallByName {
name: gen_symbol_0,
layout: Layout::FunctionPointer(
&[Layout::Builtin(Builtin::Float64)],
&Layout::Builtin(Builtin::Float64),
),
args: &[(Float(3.14), Layout::Builtin(Float64))],
},
Layout::Builtin(Float64),
),
])
},
@ -314,22 +346,31 @@ mod test_mono {
"#,
{
use self::Builtin::*;
use Layout::Builtin;
let home = test_home();
let gen_symbol_3 = Interns::from_index(home, 3);
let gen_symbol_4 = Interns::from_index(home, 4);
let gen_symbol_0 = Interns::from_index(home, 0);
CallByName(
gen_symbol_3,
&[(
CallByName {
name: gen_symbol_0,
layout: Layout::FunctionPointer(
&[Layout::Struct(&[Layout::Builtin(Builtin::Int64)])],
&Layout::Struct(&[Layout::Builtin(Builtin::Int64)]),
),
args: &[(
Struct(&[(
CallByName(gen_symbol_4, &[(Int(4), Builtin(Int64))]),
Builtin(Int64),
CallByName {
name: gen_symbol_0,
layout: Layout::FunctionPointer(
&[Layout::Builtin(Builtin::Int64)],
&Layout::Builtin(Builtin::Int64),
),
args: &[(Int(4), Layout::Builtin(Int64))],
},
Layout::Builtin(Int64),
)]),
Layout::Struct(&[Builtin(Int64)]),
Layout::Struct(&[Layout::Builtin(Int64)]),
)],
)
}
},
)
}
@ -464,13 +505,30 @@ mod test_mono {
#[test]
fn set_unique_int_list() {
compiles_to("List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1", {
CallByName(
Symbol::LIST_GET_UNSAFE,
&vec![
CallByName {
name: Symbol::LIST_GET_UNSAFE,
layout: Layout::FunctionPointer(
&[
Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
Layout::Builtin(Builtin::Int64),
],
&Layout::Builtin(Builtin::Int64),
),
args: &vec![
(
CallByName(
Symbol::LIST_SET,
&vec![
CallByName {
name: Symbol::LIST_SET,
layout: Layout::FunctionPointer(
&[
Layout::Builtin(Builtin::List(&Layout::Builtin(
Builtin::Int64,
))),
Layout::Builtin(Builtin::Int64),
Layout::Builtin(Builtin::Int64),
],
&Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
),
args: &vec![
(
Array {
elem_layout: Layout::Builtin(Builtin::Int64),
@ -483,12 +541,12 @@ mod test_mono {
(Int(1), Layout::Builtin(Builtin::Int64)),
(Int(42), Layout::Builtin(Builtin::Int64)),
],
),
},
Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
),
(Int(1), Layout::Builtin(Builtin::Int64)),
],
)
}
});
}

View File

@ -79,8 +79,15 @@ mod test_opt {
unexpected_calls: &mut Vec<Symbol>,
) {
match expr {
Int(_) | Float(_) | Str(_) | Bool(_) | Byte(_) | Load(_) | FunctionPointer(_)
| RuntimeError(_) => (),
Int(_)
| Float(_)
| Str(_)
| Bool(_)
| Byte(_)
| Load(_)
| FunctionPointer(_, _)
| RuntimeError(_)
| RuntimeErrorFunction(_) => (),
Store(paths, sub_expr) => {
for (_, _, path_expr) in paths.iter() {
@ -98,15 +105,19 @@ mod test_opt {
}
}
CallByName(symbol, args) => {
CallByName {
name,
layout: _,
args,
} => {
// Search for the symbol. If we found it, check it off the list.
// If we didn't find it, add it to the list of unexpected calls.
match calls.binary_search(symbol) {
match calls.binary_search(name) {
Ok(index) => {
calls.remove(index);
}
Err(_) => {
unexpected_calls.push(*symbol);
unexpected_calls.push(*name);
}
}
@ -222,13 +233,30 @@ mod test_opt {
// This should optimize List.set to List.set_in_place
compiles_to(
"List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1",
CallByName(
Symbol::LIST_GET_UNSAFE,
&vec![
CallByName {
name: Symbol::LIST_GET_UNSAFE,
layout: Layout::FunctionPointer(
&[
Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
Layout::Builtin(Builtin::Int64),
],
&Layout::Builtin(Builtin::Int64),
),
args: &vec![
(
CallByName(
Symbol::LIST_SET_IN_PLACE,
&vec![
CallByName {
name: Symbol::LIST_SET_IN_PLACE,
layout: Layout::FunctionPointer(
&[
Layout::Builtin(Builtin::List(&Layout::Builtin(
Builtin::Int64,
))),
Layout::Builtin(Builtin::Int64),
Layout::Builtin(Builtin::Int64),
],
&Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
),
args: &vec![
(
Array {
elem_layout: Layout::Builtin(Builtin::Int64),
@ -241,12 +269,12 @@ mod test_opt {
(Int(1), Layout::Builtin(Builtin::Int64)),
(Int(42), Layout::Builtin(Builtin::Int64)),
],
),
},
Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
),
(Int(1), Layout::Builtin(Builtin::Int64)),
],
),
},
);
}

View File

@ -562,250 +562,6 @@ pub enum FlatType {
Boolean(boolean_algebra::Bool),
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, Copy)]
pub struct ContentHash(u64);
impl ContentHash {
pub fn from_var(var: Variable, subs: &mut Subs) -> Self {
use std::hash::Hasher;
let mut hasher = std::collections::hash_map::DefaultHasher::new();
Self::from_var_help(var, subs, &mut hasher);
ContentHash(hasher.finish())
}
pub fn from_var_help<T>(var: Variable, subs: &mut Subs, hasher: &mut T)
where
T: std::hash::Hasher,
{
Self::from_content_help(var, &subs.get_without_compacting(var).content, subs, hasher)
}
pub fn from_content_help<T>(var: Variable, content: &Content, subs: &mut Subs, hasher: &mut T)
where
T: std::hash::Hasher,
{
match content {
Content::Alias(_, _, actual) => {
// ensure an alias has the same hash as just the body of the alias
Self::from_var_help(*actual, subs, hasher)
}
Content::Structure(flat_type) => {
hasher.write_u8(0x10);
Self::from_flat_type_help(var, flat_type, subs, hasher)
}
Content::FlexVar(_) | Content::RigidVar(_) => {
hasher.write_u8(0x11);
}
Content::Error => {
hasher.write_u8(0x12);
}
}
}
pub fn from_flat_type_help<T>(
flat_type_var: Variable,
flat_type: &FlatType,
subs: &mut Subs,
hasher: &mut T,
) where
T: std::hash::Hasher,
{
use std::hash::Hash;
match flat_type {
FlatType::Func(arguments, ret) => {
hasher.write_u8(0);
for var in arguments {
Self::from_var_help(*var, subs, hasher);
}
Self::from_var_help(*ret, subs, hasher);
}
FlatType::Apply(symbol, arguments) => {
hasher.write_u8(1);
symbol.hash(hasher);
for var in arguments {
Self::from_var_help(*var, subs, hasher);
}
}
FlatType::EmptyRecord => {
hasher.write_u8(2);
}
FlatType::Record(record_fields, ext) => {
hasher.write_u8(3);
// NOTE: This function will modify the subs, putting all fields from the ext_var
// into the record itself, then setting the ext_var to EMPTY_RECORD
let mut fields = Vec::with_capacity(record_fields.len());
let mut extracted_fields_from_ext = false;
if *ext != Variable::EMPTY_RECORD {
let mut fields_map = MutMap::default();
match crate::pretty_print::chase_ext_record(subs, *ext, &mut fields_map) {
Err((_, Content::FlexVar(_))) | Ok(()) => {
if !fields_map.is_empty() {
extracted_fields_from_ext = true;
fields.extend(fields_map.into_iter());
}
}
Err(content) => panic!("Record with unexpected ext_var: {:?}", content),
}
}
fields.extend(record_fields.clone().into_iter());
fields.sort();
for (name, argument) in &fields {
name.hash(hasher);
Self::from_var_help(*argument, subs, hasher);
}
if *ext != Variable::EMPTY_RECORD {
// unify ext with empty record
let desc = subs.get(Variable::EMPTY_RECORD);
subs.union(Variable::EMPTY_RECORD, *ext, desc);
}
if extracted_fields_from_ext {
let fields_map = fields.into_iter().collect();
subs.set_content(
flat_type_var,
Content::Structure(FlatType::Record(fields_map, Variable::EMPTY_RECORD)),
);
}
}
FlatType::EmptyTagUnion => {
hasher.write_u8(4);
}
FlatType::TagUnion(tags, ext) => {
hasher.write_u8(5);
// NOTE: This function will modify the subs, putting all tags from the ext_var
// into the tag union itself, then setting the ext_var to EMPTY_TAG_UNION
let mut tag_vec = Vec::with_capacity(tags.len());
let mut extracted_fields_from_ext = false;
if *ext != Variable::EMPTY_TAG_UNION {
match crate::pretty_print::chase_ext_tag_union(subs, *ext, &mut tag_vec) {
Err((_, Content::FlexVar(_))) | Ok(()) => {
extracted_fields_from_ext = !tag_vec.is_empty();
}
Err(content) => panic!("TagUnion with unexpected ext_var: {:?}", content),
}
}
tag_vec.extend(tags.clone().into_iter());
tag_vec.sort();
for (name, arguments) in &tag_vec {
name.hash(hasher);
for var in arguments {
Self::from_var_help(*var, subs, hasher);
}
}
if *ext != Variable::EMPTY_TAG_UNION {
// unify ext with empty record
let desc = subs.get(Variable::EMPTY_TAG_UNION);
subs.union(Variable::EMPTY_TAG_UNION, *ext, desc);
}
if extracted_fields_from_ext {
let fields_map = tag_vec.into_iter().collect();
subs.set_content(
flat_type_var,
Content::Structure(FlatType::TagUnion(
fields_map,
Variable::EMPTY_TAG_UNION,
)),
);
}
}
FlatType::RecursiveTagUnion(rec, tags, ext) => {
// NOTE: rec is not hashed in. If all the tags and their arguments are the same,
// then the recursive tag unions are the same
hasher.write_u8(6);
// NOTE: This function will modify the subs, putting all tags from the ext_var
// into the tag union itself, then setting the ext_var to EMPTY_TAG_UNION
let mut tag_vec = Vec::with_capacity(tags.len());
let mut extracted_fields_from_ext = false;
if *ext != Variable::EMPTY_TAG_UNION {
match crate::pretty_print::chase_ext_tag_union(subs, *ext, &mut tag_vec) {
Err((_, Content::FlexVar(_))) | Ok(()) => {
extracted_fields_from_ext = !tag_vec.is_empty();
}
Err(content) => {
panic!("RecursiveTagUnion with unexpected ext_var: {:?}", content)
}
}
}
tag_vec.extend(tags.clone().into_iter());
tag_vec.sort();
for (name, arguments) in &tag_vec {
name.hash(hasher);
for var in arguments {
Self::from_var_help(*var, subs, hasher);
}
}
if *ext != Variable::EMPTY_TAG_UNION {
// unify ext with empty record
let desc = subs.get(Variable::EMPTY_TAG_UNION);
subs.union(Variable::EMPTY_TAG_UNION, *ext, desc);
}
if extracted_fields_from_ext {
let fields_map = tag_vec.into_iter().collect();
subs.set_content(
flat_type_var,
Content::Structure(FlatType::RecursiveTagUnion(
*rec,
fields_map,
Variable::EMPTY_TAG_UNION,
)),
);
}
}
FlatType::Boolean(boolean) => {
hasher.write_u8(7);
match boolean.simplify(subs) {
Ok(_variables) => hasher.write_u8(1),
Err(crate::boolean_algebra::Atom::One) => hasher.write_u8(1),
Err(crate::boolean_algebra::Atom::Zero) => hasher.write_u8(0),
Err(crate::boolean_algebra::Atom::Variable(_)) => unreachable!(),
}
}
FlatType::Erroneous(_problem) => {
hasher.write_u8(8);
//TODO hash the problem?
}
}
}
}
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum Builtin {
Str,