mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-22 08:17:40 +03:00
Merge pull request #355 from rtfeldman/call-by-layout
Call by (name + layout) in code gen
This commit is contained in:
commit
f688236118
@ -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));
|
||||
|
@ -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));
|
||||
|
51
compiler/gen/src/layout_id.rs
Normal file
51
compiler/gen/src/layout_id.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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 =
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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)),
|
||||
],
|
||||
)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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)),
|
||||
],
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user