diff --git a/cli/src/main.rs b/cli/src/main.rs index 3a4635336c..f709d265dd 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -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)); diff --git a/cli/src/repl.rs b/cli/src/repl.rs index 08212d9d97..3071c6a223 100644 --- a/cli/src/repl.rs +++ b/cli/src/repl.rs @@ -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)); diff --git a/compiler/gen/src/layout_id.rs b/compiler/gen/src/layout_id.rs new file mode 100644 index 0000000000..06133558b4 --- /dev/null +++ b/compiler/gen/src/layout_id.rs @@ -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, u32>, + next_id: u32, +} + +#[derive(Default)] +pub struct LayoutIds<'a> { + by_symbol: MutMap>, +} + +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) + } +} diff --git a/compiler/gen/src/lib.rs b/compiler/gen/src/lib.rs index 89bd8eb2fd..f053e8771d 100644 --- a/compiler/gen/src/lib.rs +++ b/compiler/gen/src/lib.rs @@ -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; diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 7c394d6c0e..9af59f57ed 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -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>, 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 = 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 = Vec::with_capacity_in(args.len(), env.arena); diff --git a/compiler/gen/tests/helpers/eval.rs b/compiler/gen/tests/helpers/eval.rs index f8ccbb718d..6a42c8d117 100644 --- a/compiler/gen/tests/helpers/eval.rs +++ b/compiler/gen/tests/helpers/eval.rs @@ -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 = diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 5b63a22c8e..c898a72b3b 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -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 diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index e1ae30b541..57442daea6 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -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 diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 25f209f17f..6e9bb3d8f8 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -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>, pub module_thunks: MutSet, - anonymous: MutMap>>, - specializations: MutMap>)>, + runtime_errors: MutSet, + specializations: MutMap, Proc<'a>>>, + pending_specializations: MutMap>, builtin: MutSet, } @@ -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)>, loc_body: Located, ret_var: Variable, - ) { + layout_cache: &mut LayoutCache<'a>, + ) -> Result, ()> { 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>, - ) { - 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>> { - 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, Proc<'a>>>, MutSet) { + 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, + 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, ret_expr: Located, + 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, mut branches: std::vec::Vec, + 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)>, + 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::>(); + + 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::>(); - - 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, ()> { // 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)], + 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, + } + } } diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 80aca88920..18a5a80764 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -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 { - 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 { + 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, ()>>, +} + +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, ()> { + // 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::() as u32; const F64_SIZE: u32 = std::mem::size_of::() 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); } diff --git a/compiler/mono/src/lib.rs b/compiler/mono/src/lib.rs index 5743c9c50f..8b78927d46 100644 --- a/compiler/mono/src/lib.rs +++ b/compiler/mono/src/lib.rs @@ -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; diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index f6a51c54dd..b775a7f4ac 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -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)), ], - ) + } }); } diff --git a/compiler/mono/tests/test_opt.rs b/compiler/mono/tests/test_opt.rs index f9a69df0a0..8f2498cb7b 100644 --- a/compiler/mono/tests/test_opt.rs +++ b/compiler/mono/tests/test_opt.rs @@ -79,8 +79,15 @@ mod test_opt { unexpected_calls: &mut Vec, ) { 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)), ], - ), + }, ); } diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index b1e99c5e80..e35a45f9a6 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -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(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(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( - 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,