use crate::layout::{Builtin, Layout}; use crate::pattern::{Ctor, Guard, RenderAs, TagId}; use bumpalo::collections::Vec; use bumpalo::Bump; use roc_collections::all::{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 std::hash::Hash; #[derive(Clone, Debug, PartialEq)] pub struct PartialProc<'a> { pub annotation: Variable, pub patterns: Vec<'a, Symbol>, pub body: roc_can::expr::Expr, } #[derive(Clone, Debug, PartialEq)] pub struct Proc<'a> { pub name: Symbol, pub args: &'a [(Layout<'a>, Symbol)], pub body: Expr<'a>, pub closes_over: Layout<'a>, pub ret_layout: Layout<'a>, } #[derive(Clone, Debug, PartialEq, Default)] pub struct Procs<'a> { pub user_defined: MutMap>, pub module_thunks: MutSet, anonymous: MutMap>>, specializations: MutMap>)>, builtin: MutSet, } impl<'a> Procs<'a> { fn insert_user_defined(&mut self, symbol: Symbol, partial_proc: PartialProc<'a>) { self.user_defined.insert(symbol, partial_proc); } fn insert_anonymous(&mut self, symbol: Symbol, proc: Option>) { self.anonymous.insert(symbol, proc); } pub fn insert_closure( &mut self, env: &mut Env<'a, '_>, name: Option, annotation: Variable, loc_args: std::vec::Vec<(Variable, Located)>, loc_body: Located, ret_var: Variable, ) -> Symbol { // turn record/tag patterns into a when expression, e.g. // // foo = \{ x } -> body // // becomes // // foo = \r -> when r is { x } -> body // // conversion of one-pattern when expressions will do the most optimal thing let (arg_vars, arg_symbols, body) = patterns_to_when(env, loc_args, ret_var, loc_body); match name { Some(symbol) => { // a named closure self.insert_user_defined( symbol, PartialProc { annotation, patterns: arg_symbols, body: body.value, }, ); symbol } None => { // an anonymous closure. These will always be specialized already // by the surrounding context let symbol = env.fresh_symbol(); let opt_proc = specialize_proc_body( env, self, annotation, ret_var, symbol, &arg_vars, &arg_symbols, annotation, body.value, ) .ok(); self.insert_anonymous(symbol, opt_proc); symbol } } } fn insert_specialization( &mut self, hash: ContentHash, spec_name: Symbol, proc: Option>, ) { self.specializations.insert(hash, (spec_name, proc)); } fn get_user_defined(&self, symbol: Symbol) -> Option<&PartialProc<'a>> { self.user_defined.get(&symbol) } pub fn len(&self) -> usize { let anonymous: usize = self.anonymous.len(); let user_defined: usize = self.specializations.len(); anonymous + user_defined } pub fn is_empty(&self) -> bool { self.len() == 0 } fn insert_builtin(&mut self, symbol: Symbol) { 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); } for symbol in self.builtin.iter() { result.insert(*symbol, None); } result } } pub struct Env<'a, 'i> { pub arena: &'a Bump, pub subs: &'a mut Subs, pub problems: &'i mut std::vec::Vec, pub home: ModuleId, pub ident_ids: &'i mut IdentIds, pub pointer_size: u32, pub jump_counter: &'a mut u64, pub symbol_counter: usize, } impl<'a, 'i> Env<'a, 'i> { pub fn fresh_symbol(&mut self) -> Symbol { let ident_id = self .ident_ids .add(format!("_{}", self.symbol_counter).into()); self.symbol_counter += 1; self.home.register_debug_idents(&self.ident_ids); Symbol::new(self.home, ident_id) } } pub type Stores<'a> = &'a [(Symbol, Layout<'a>, Expr<'a>)]; #[derive(Clone, Debug, PartialEq)] pub enum Expr<'a> { // Literals Int(i64), Float(f64), Str(&'a str), /// Closed tag unions containing exactly two (0-arity) tags compile to Expr::Bool, /// so they can (at least potentially) be emitted as 1-bit machine bools. /// /// So [ True, False ] compiles to this, and so do [ A, B ] and [ Foo, Bar ]. /// However, a union like [ True, False, Other Int ] would not. Bool(bool), /// Closed tag unions containing between 3 and 256 tags (all of 0 arity) /// compile to bytes, e.g. [ Blue, Black, Red, Green, White ] Byte(u8), // Load/Store Load(Symbol), Store(&'a [(Symbol, Layout<'a>, Expr<'a>)], &'a Expr<'a>), // Functions FunctionPointer(Symbol), CallByName(Symbol, &'a [(Expr<'a>, Layout<'a>)]), CallByPointer(&'a Expr<'a>, &'a [Expr<'a>], Layout<'a>), // Exactly two conditional branches, e.g. if/else Cond { // The left-hand side of the conditional comparison and the right-hand side. // These are stored separately because there are different machine instructions // for e.g. "compare float and jump" vs. "compare integer and jump" // symbol storing the original expression that we branch on, e.g. `Ok 42` // required for RC logic cond_symbol: Symbol, // symbol storing the value that we branch on, e.g. `1` representing the `Ok` tag branch_symbol: Symbol, cond_layout: Layout<'a>, // What to do if the condition either passes or fails pass: (Stores<'a>, &'a Expr<'a>), fail: (Stores<'a>, &'a Expr<'a>), ret_layout: Layout<'a>, }, /// Conditional branches for integers. These are more efficient. Switch { /// This *must* be an integer, because Switch potentially compiles to a jump table. cond: &'a Expr<'a>, cond_layout: Layout<'a>, /// The u64 in the tuple will be compared directly to the condition Expr. /// If they are equal, this branch will be taken. branches: &'a [(u64, Stores<'a>, Expr<'a>)], /// If no other branches pass, this default branch will be taken. default_branch: (Stores<'a>, &'a Expr<'a>), /// Each branch must return a value of this type. ret_layout: Layout<'a>, }, Tag { tag_layout: Layout<'a>, tag_name: TagName, tag_id: u8, union_size: u8, arguments: &'a [(Expr<'a>, Layout<'a>)], }, Struct(&'a [(Expr<'a>, Layout<'a>)]), AccessAtIndex { index: u64, field_layouts: &'a [Layout<'a>], expr: &'a Expr<'a>, is_unwrapped: bool, }, Array { elem_layout: Layout<'a>, elems: &'a [Expr<'a>], }, RuntimeError(&'a str), } #[derive(Clone, Debug)] pub enum MonoProblem { PatternProblem(crate::pattern::Error), } #[allow(clippy::too_many_arguments)] impl<'a> Expr<'a> { pub fn new( env: &mut Env<'a, '_>, can_expr: roc_can::expr::Expr, procs: &mut Procs<'a>, ) -> Self { from_can(env, can_expr, procs, None) } } enum IntOrFloat { IntType, FloatType, } /// Given the `a` in `Num a`, determines whether it's an int or a float fn num_argument_to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat { match subs.get_without_compacting(var).content { Content::Alias(Symbol::INT_INTEGER, args, _) => { debug_assert!(args.is_empty()); IntOrFloat::IntType } Content::FlexVar(_) => { // If this was still a (Num *), assume compiling it to an Int IntOrFloat::IntType } Content::Alias(Symbol::FLOAT_FLOATINGPOINT, args, _) => { debug_assert!(args.is_empty()); IntOrFloat::FloatType } Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, attr_args)) => { debug_assert!(attr_args.len() == 2); // Recurse on the second argument num_argument_to_int_or_float(subs, attr_args[1]) } other => { panic!( "Unrecognized Num type argument for var {:?} with Content: {:?}", var, other ); } } } /// Given a `Num a`, determines whether it's an int or a float fn num_to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat { match subs.get_without_compacting(var).content { Content::Alias(Symbol::NUM_NUM, args, _) => { debug_assert!(args.len() == 1); num_argument_to_int_or_float(subs, args[0].1) } Content::Alias(Symbol::INT_INT, _, _) => IntOrFloat::IntType, Content::Alias(Symbol::FLOAT_FLOAT, _, _) => IntOrFloat::FloatType, Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, attr_args)) => { debug_assert!(attr_args.len() == 2); // Recurse on the second argument num_to_int_or_float(subs, attr_args[1]) } other => { panic!( "Input variable is not a Num, but {:?} is a {:?}", var, other ); } } } fn patterns_to_when<'a>( env: &mut Env<'a, '_>, patterns: std::vec::Vec<(Variable, Located)>, body_var: Variable, mut body: Located, ) -> ( Vec<'a, Variable>, Vec<'a, Symbol>, Located, ) { let mut arg_vars = Vec::with_capacity_in(patterns.len(), env.arena); let mut symbols = Vec::with_capacity_in(patterns.len(), env.arena); // patterns that are not yet in a when (e.g. in let or function arguments) must be irrefutable // to pass type checking. So the order in which we add them to the body does not matter: there // are only stores anyway, no branches. for (pattern_var, pattern) in patterns.into_iter() { let context = crate::pattern::Context::BadArg; let mono_pattern = from_can_pattern(env, &pattern.value); match crate::pattern::check( pattern.region, &[( Located::at(pattern.region, mono_pattern.clone()), crate::pattern::Guard::NoGuard, )], context, ) { Ok(_) => { let (new_symbol, new_body) = pattern_to_when(env, pattern_var, pattern, body_var, body); symbols.push(new_symbol); body = new_body; } Err(errors) => { for error in errors { env.problems.push(MonoProblem::PatternProblem(error)) } let error = roc_problem::can::RuntimeError::UnsupportedPattern(pattern.region); body = Located::at(pattern.region, roc_can::expr::Expr::RuntimeError(error)); } } arg_vars.push(pattern_var); } (arg_vars, symbols, body) } /// turn irrefutable patterns into when. For example /// /// foo = \{ x } -> body /// /// Assuming the above program typechecks, the pattern match cannot fail /// (it is irrefutable). It becomes /// /// foo = \r -> /// when r is /// { x } -> body /// /// conversion of one-pattern when expressions will do the most optimal thing fn pattern_to_when<'a>( env: &mut Env<'a, '_>, pattern_var: Variable, pattern: Located, body_var: Variable, body: Located, ) -> (Symbol, Located) { use roc_can::expr::Expr::*; use roc_can::expr::WhenBranch; use roc_can::pattern::Pattern::*; match &pattern.value { Identifier(symbol) => (*symbol, body), Underscore => { // for underscore we generate a dummy Symbol (env.fresh_symbol(), body) } Shadowed(region, loc_ident) => { let error = roc_problem::can::RuntimeError::Shadowing { original_region: *region, shadow: loc_ident.clone(), }; (env.fresh_symbol(), Located::at_zero(RuntimeError(error))) } UnsupportedPattern(region) => { // create the runtime error here, instead of delegating to When. // UnsupportedPattern should then never occcur in When let error = roc_problem::can::RuntimeError::UnsupportedPattern(*region); (env.fresh_symbol(), Located::at_zero(RuntimeError(error))) } AppliedTag { .. } | RecordDestructure { .. } => { let symbol = env.fresh_symbol(); let wrapped_body = When { cond_var: pattern_var, expr_var: body_var, region: Region::zero(), loc_cond: Box::new(Located::at_zero(Var(symbol))), branches: vec![WhenBranch { patterns: vec![pattern], value: body, guard: None, }], }; (symbol, Located::at_zero(wrapped_body)) } IntLiteral(_) | NumLiteral(_, _) | FloatLiteral(_) | StrLiteral(_) => { // These patters are refutable, and thus should never occur outside a `when` expression // They should have been replaced with `UnsupportedPattern` during canonicalization unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value) } } } #[allow(clippy::cognitive_complexity)] fn from_can<'a>( env: &mut Env<'a, '_>, can_expr: roc_can::expr::Expr, procs: &mut Procs<'a>, name: Option, ) -> Expr<'a> { use roc_can::expr::Expr::*; match can_expr { Num(var, num) => match num_argument_to_int_or_float(env.subs, var) { IntOrFloat::IntType => Expr::Int(num), IntOrFloat::FloatType => Expr::Float(num as f64), }, Int(_, num) => Expr::Int(num), Float(_, num) => Expr::Float(num), Str(string) | BlockStr(string) => Expr::Str(env.arena.alloc(string)), Var(symbol) => { if procs.module_thunks.contains(&symbol) { let partial_proc = procs.get_user_defined(symbol).unwrap(); let fn_var = partial_proc.annotation; 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()) } 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), Closure(annotation, _, _, loc_args, boxed_body) => { let (loc_body, ret_var) = *boxed_body; let symbol = procs.insert_closure(env, name, annotation, loc_args, loc_body, ret_var); 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) { 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) { Symbol::LIST_SET => { let subs = &env.subs; // The first arg is the one with the List in it. // List.set : List elem, Int, elem -> List elem let (list_arg_var, _) = loc_args.get(0).unwrap(); let content = subs.get_without_compacting(*list_arg_var).content; match content { Content::Structure(FlatType::Apply( Symbol::ATTR_ATTR, attr_args, )) => { debug_assert!(attr_args.len() == 2); // If the first argument (the List) is unique, // then we can safely upgrade to List.set_in_place let attr_arg_content = subs.get_without_compacting(attr_args[0]).content; let new_name = if attr_arg_content.is_unique(subs) { Symbol::LIST_SET_IN_PLACE } else { 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, proc_name, loc_args), } } specialized_proc_symbol => call_by_name( env, procs, fn_var, ret_var, specialized_proc_symbol, loc_args, ), } } ptr => { // Call by pointer - the closure was anonymous, e.g. // // ((\a -> a) 5) // // It might even be the anonymous result of a conditional: // // ((if x > 0 then \a -> a else \_ -> 0) 5) // // It could be named too: // // ((if x > 0 then foo else bar) 5) 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)); } let layout = Layout::from_var(env.arena, fn_var, env.subs, env.pointer_size) .unwrap_or_else(|err| { panic!("TODO turn fn_var into a RuntimeError {:?}", err) }); Expr::CallByPointer(&*env.arena.alloc(ptr), args.into_bump_slice(), layout) } } } When { cond_var, expr_var, region, loc_cond, branches, } => from_can_when(env, cond_var, expr_var, region, *loc_cond, branches, procs), If { cond_var, branch_var, branches, final_else, } => { let mut expr = from_can(env, final_else.value, procs, None); let ret_layout = Layout::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) .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 branch_symbol = env.fresh_symbol(); let cond_expr = Expr::Cond { cond_symbol: branch_symbol, branch_symbol, cond_layout: cond_layout.clone(), pass: (&[], env.arena.alloc(then)), fail: (&[], env.arena.alloc(expr)), ret_layout: ret_layout.clone(), }; expr = Expr::Store( env.arena .alloc(vec![(branch_symbol, Layout::Builtin(Builtin::Bool), cond)]), env.arena.alloc(cond_expr), ); } expr } Record { record_var, mut fields, .. } => { let arena = env.arena; let btree = crate::layout::record_fields_btree( env.arena, record_var, env.subs, env.pointer_size, ); let mut field_tuples = Vec::with_capacity_in(btree.len(), arena); for (label, layout) in btree { let field = fields.remove(&label).unwrap(); let expr = from_can(env, field.loc_expr.value, procs, None); field_tuples.push((expr, layout)); } Expr::Struct(field_tuples.into_bump_slice()) } EmptyRecord => Expr::Struct(&[]), Tag { variant_var, name: tag_name, arguments: args, .. } => { use crate::layout::UnionVariant::*; let arena = env.arena; let variant = crate::layout::union_sorted_tags( env.arena, variant_var, env.subs, env.pointer_size, ); match variant { Never => unreachable!("The `[]` type has no constructors"), Unit => Expr::Struct(&[]), BoolUnion { ttrue, .. } => Expr::Bool(tag_name == ttrue), ByteUnion(tag_names) => { let tag_id = tag_names .iter() .position(|key| key == &tag_name) .expect("tag must be in its own type"); Expr::Byte(tag_id as u8) } Unwrapped(field_layouts) => { let field_exprs = args .into_iter() .map(|(_, arg)| from_can(env, arg.value, procs, None)); let mut field_tuples = Vec::with_capacity_in(field_layouts.len(), arena); for tuple in field_exprs.zip(field_layouts.into_iter()) { field_tuples.push(tuple) } Expr::Struct(field_tuples.into_bump_slice()) } Wrapped(sorted_tag_layouts) => { let union_size = sorted_tag_layouts.len() as u8; let (tag_id, (_, argument_layouts)) = sorted_tag_layouts .iter() .enumerate() .find(|(_, (key, _))| key == &tag_name) .expect("tag must be in its own type"); let mut arguments = Vec::with_capacity_in(args.len(), arena); let it = std::iter::once(Expr::Int(tag_id as i64)).chain( args.into_iter() .map(|(_, arg)| from_can(env, arg.value, procs, None)), ); for (arg_layout, arg_expr) in argument_layouts.iter().zip(it) { arguments.push((arg_expr, arg_layout.clone())); } let mut layouts: Vec<&'a [Layout<'a>]> = Vec::with_capacity_in(sorted_tag_layouts.len(), env.arena); for (_, arg_layouts) in sorted_tag_layouts.into_iter() { layouts.push(arg_layouts); } let layout = Layout::Union(layouts.into_bump_slice()); Expr::Tag { tag_layout: layout, tag_name, tag_id: tag_id as u8, union_size, arguments: arguments.into_bump_slice(), } } } } Access { record_var, field, loc_expr, .. } => { let arena = env.arena; let btree = crate::layout::record_fields_btree( env.arena, record_var, env.subs, env.pointer_size, ); let mut index = None; let mut field_layouts = Vec::with_capacity_in(btree.len(), env.arena); for (current, (label, field_layout)) in btree.into_iter().enumerate() { field_layouts.push(field_layout); if label == field { index = Some(current); } } let record = arena.alloc(from_can(env, loc_expr.value, procs, None)); Expr::AccessAtIndex { index: index.expect("field not in its own type") as u64, field_layouts: field_layouts.into_bump_slice(), expr: record, is_unwrapped: true, } } List { elem_var, loc_elems, } => { let arena = env.arena; let subs = &env.subs; let elem_content = subs.get_without_compacting(elem_var).content; let elem_layout = match elem_content { // 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, Err(()) => { panic!("TODO gracefully handle List with invalid element layout"); } }, }; 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)); } Expr::Array { elem_layout, elems: elems.into_bump_slice(), } } Accessor { .. } => todo!("record accessor"), Update { .. } => todo!("record update"), RuntimeError(error) => Expr::RuntimeError(env.arena.alloc(format!("{:?}", error))), } } fn store_pattern<'a>( env: &mut Env<'a, '_>, can_pat: &Pattern<'a>, outer_symbol: Symbol, layout: Layout<'a>, stored: &mut Vec<'a, (Symbol, Layout<'a>, Expr<'a>)>, ) -> Result<(), String> { use Pattern::*; match can_pat { Identifier(symbol) => { let load = Expr::Load(outer_symbol); stored.push((*symbol, layout, load)) } Underscore => { // Since _ is never read, it's safe to reassign it. // stored.push((Symbol::UNDERSCORE, layout, Expr::Load(outer_symbol))) } IntLiteral(_) | FloatLiteral(_) | EnumLiteral { .. } | BitLiteral { .. } => {} AppliedTag { union, arguments, .. } => { let is_unwrapped = union.alternatives.len() == 1; let mut arg_layouts = Vec::with_capacity_in(arguments.len(), env.arena); if !is_unwrapped { // add an element for the tag discriminant arg_layouts.push(Layout::Builtin(Builtin::Int64)); } for (_, layout) in arguments { arg_layouts.push(layout.clone()); } for (index, (argument, arg_layout)) in arguments.iter().enumerate() { let load = Expr::AccessAtIndex { is_unwrapped, index: (!is_unwrapped as usize + index) as u64, field_layouts: arg_layouts.clone().into_bump_slice(), expr: env.arena.alloc(Expr::Load(outer_symbol)), }; match argument { Identifier(symbol) => { // store immediately in the given symbol stored.push((*symbol, arg_layout.clone(), load)); } Underscore => { // ignore } IntLiteral(_) | FloatLiteral(_) | EnumLiteral { .. } | BitLiteral { .. } => {} _ => { // store the field in a symbol, and continue matching on it let symbol = env.fresh_symbol(); stored.push((symbol, arg_layout.clone(), load)); store_pattern(env, argument, symbol, arg_layout.clone(), stored)?; } } } } RecordDestructure(destructs, Layout::Struct(sorted_fields)) => { for (index, destruct) in destructs.iter().enumerate() { store_record_destruct( env, destruct, index as u64, outer_symbol, sorted_fields, stored, )?; } } Shadowed(region, ident) => { return Err(format!( "The pattern at {:?} shadows variable {:?}", region, ident )); } _ => { panic!("TODO store_pattern for {:?}", can_pat); } } Ok(()) } fn store_record_destruct<'a>( env: &mut Env<'a, '_>, destruct: &RecordDestruct<'a>, index: u64, outer_symbol: Symbol, sorted_fields: &'a [Layout<'a>], stored: &mut Vec<'a, (Symbol, Layout<'a>, Expr<'a>)>, ) -> Result<(), String> { use Pattern::*; let record = env.arena.alloc(Expr::Load(outer_symbol)); let load = Expr::AccessAtIndex { index, field_layouts: sorted_fields, expr: record, is_unwrapped: true, }; match &destruct.guard { None => { stored.push((destruct.symbol, destruct.layout.clone(), load)); } Some(guard_pattern) => match &guard_pattern { Identifier(symbol) => { stored.push((*symbol, destruct.layout.clone(), load)); } Underscore => { // important that this is special-cased to do nothing: mono record patterns will extract all the // fields, but those not bound in the source code are guarded with the underscore // pattern. So given some record `{ x : a, y : b }`, a match // // { x } -> ... // // is actually // // { x, y: _ } -> ... // // internally. But `y` is never used, so we must make sure it't not stored/loaded. } IntLiteral(_) | FloatLiteral(_) | EnumLiteral { .. } | BitLiteral { .. } => {} _ => { let symbol = env.fresh_symbol(); stored.push((symbol, destruct.layout.clone(), load)); store_pattern(env, guard_pattern, symbol, destruct.layout.clone(), stored)?; } }, } Ok(()) } fn from_can_defs<'a>( env: &mut Env<'a, '_>, defs: std::vec::Vec, ret_expr: Located, procs: &mut Procs<'a>, ) -> Expr<'a> { use roc_can::expr::Expr::*; use roc_can::pattern::Pattern::*; 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; // If we're defining a named closure, insert it into Procs and then // remove the Let. When code gen later goes to look it up, it'll be in Procs! // // Before: // // identity = \a -> a // // identity 5 // // After: (`identity` is now in Procs) // // identity 5 // 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)); continue; } } // 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) .expect("invalid layout"); match &mono_pattern { Pattern::Identifier(symbol) => { stored.push(( *symbol, layout.clone(), from_can(env, loc_expr.value, procs, None), )); } _ => { let context = crate::pattern::Context::BadDestruct; match crate::pattern::check( loc_pattern.region, &[( Located::at(loc_pattern.region, mono_pattern.clone()), crate::pattern::Guard::NoGuard, )], context, ) { Ok(_) => {} Err(errors) => { for error in errors { env.problems.push(MonoProblem::PatternProblem(error)) } // panic!("generate runtime error, should probably also optimize this"); } } let symbol = env.fresh_symbol(); stored.push(( symbol, layout.clone(), from_can(env, loc_expr.value, procs, None), )); match store_pattern(env, &mono_pattern, symbol, layout, &mut stored) { Ok(()) => {} Err(message) => todo!( "generate runtime error, the pattern was invalid: {:?}", message ), } } } } // 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); if stored.is_empty() { ret } else { Expr::Store(stored.into_bump_slice(), arena.alloc(ret)) } } fn from_can_when<'a>( env: &mut Env<'a, '_>, cond_var: Variable, expr_var: Variable, region: Region, loc_cond: Located, mut branches: std::vec::Vec, procs: &mut Procs<'a>, ) -> Expr<'a> { if branches.is_empty() { // A when-expression with no branches is a runtime error. // We can't know what to return! Expr::RuntimeError("Hit a 0-branch when expression") } else if branches.len() == 1 && branches[0].patterns.len() == 1 && branches[0].guard.is_none() { let first = branches.remove(0); // A when-expression with exactly 1 branch is essentially a LetNonRec. // As such, we can compile it direcly to a Store. let arena = env.arena; let mut stored = Vec::with_capacity_in(1, arena); let loc_when_pattern = &first.patterns[0]; let mono_pattern = from_can_pattern(env, &loc_when_pattern.value); // record pattern matches can have 1 branch and typecheck, but may still not be exhaustive let guard = if first.guard.is_some() { Guard::HasGuard } else { Guard::NoGuard }; let context = crate::pattern::Context::BadCase; match crate::pattern::check( region, &[( Located::at(loc_when_pattern.region, mono_pattern.clone()), guard, )], context, ) { Ok(_) => {} Err(errors) => { for error in errors { env.problems.push(MonoProblem::PatternProblem(error)) } // panic!("generate runtime error, should probably also optimize this"); } } let cond_layout = Layout::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.fresh_symbol(); let cond = from_can(env, loc_cond.value, procs, None); 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), 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) .unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err)); let cond = from_can(env, loc_cond.value, procs, None); let cond_symbol = env.fresh_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 exhaustive_guard = if when_branch.guard.is_some() { Guard::HasGuard } else { Guard::NoGuard }; for loc_pattern in when_branch.patterns { let mono_pattern = from_can_pattern(env, &loc_pattern.value); loc_branches.push(( Located::at(loc_pattern.region, mono_pattern.clone()), exhaustive_guard.clone(), )); let mut stores = Vec::with_capacity_in(1, env.arena); let (mono_guard, stores, expr) = match store_pattern( env, &mono_pattern, cond_symbol, cond_layout.clone(), &mut stores, ) { Ok(_) => { // if the branch is guarded, the guard can use variables bound in the // pattern. They must be available, so we give the stores to the // decision_tree. A branch with guard can only be entered with the guard // evaluated, so variables will also be loaded in the branch's body expr. // // 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); ( crate::decision_tree::Guard::Guard { stores: stores.into_bump_slice(), expr, }, &[] as &[_], mono_expr.clone(), ) } else { ( crate::decision_tree::Guard::NoGuard, stores.into_bump_slice(), mono_expr.clone(), ) } } Err(message) => { // when the pattern is invalid, a guard must give a runtime error too if when_branch.guard.is_some() { ( crate::decision_tree::Guard::Guard { stores: &[], expr: Expr::RuntimeError(env.arena.alloc(message)), }, &[] as &[_], // we can never hit this Expr::RuntimeError(&"invalid pattern with guard: unreachable"), ) } else { ( crate::decision_tree::Guard::NoGuard, &[] as &[_], Expr::RuntimeError(env.arena.alloc(message)), ) } } }; opt_branches.push((mono_pattern, mono_guard, stores, expr)); } } let context = crate::pattern::Context::BadCase; match crate::pattern::check(region, &loc_branches, context) { Ok(_) => {} Err(errors) => { use crate::pattern::Error::*; let mut is_not_exhaustive = false; let mut overlapping_branches = std::vec::Vec::new(); for error in errors { match &error { Incomplete(_, _, _) => { is_not_exhaustive = true; } Redundant { index, .. } => { overlapping_branches.push(index.to_zero_based()); } } env.problems.push(MonoProblem::PatternProblem(error)) } overlapping_branches.sort(); for i in overlapping_branches.into_iter().rev() { opt_branches.remove(i); } if is_not_exhaustive { opt_branches.push(( Pattern::Underscore, crate::decision_tree::Guard::NoGuard, &[], Expr::RuntimeError("non-exhaustive pattern match"), )); } } } let ret_layout = Layout::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( env, cond_symbol, cond_layout.clone(), ret_layout, opt_branches, ); let stores = env.arena.alloc([(cond_symbol, cond_layout, cond)]); Expr::Store(stores, env.arena.alloc(branching)) } } fn call_by_name<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, fn_var: Variable, ret_var: Variable, proc_name: Symbol, loc_args: std::vec::Vec<(Variable, Located)>, ) -> Expr<'a> { // create specialized procedure to call // If we need to specialize the body, this will get populated with the info // we need to do that. This is defined outside the procs.get_user_defined(...) call // because if we tried to specialize the body inside that match, we would // 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 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, use its symbol specialization.0 } None => { opt_specialize_body = Some(( content_hash, partial_proc.annotation, partial_proc.body.clone(), partial_proc.patterns.clone(), )); // generate a symbol for this specialization env.fresh_symbol() } } } 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 } }; 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)] fn specialize_proc_body<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, fn_var: Variable, ret_var: Variable, proc_name: Symbol, loc_args: &[Variable], pattern_symbols: &[Symbol], annotation: Variable, body: roc_can::expr::Expr, ) -> 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); // 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)?; proc_args.push((layout, *arg_name)); } let ret_layout = Layout::from_var(&env.arena, ret_var, env.subs, env.pointer_size) .unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err)); let proc = Proc { name: proc_name, args: proc_args.into_bump_slice(), body: specialized_body, closes_over: Layout::Struct(&[]), ret_layout, }; Ok(proc) } /// A pattern, including possible problems (e.g. shadowing) so that /// codegen can generate a runtime error if this pattern is reached. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Pattern<'a> { Identifier(Symbol), Underscore, IntLiteral(i64), FloatLiteral(u64), BitLiteral { value: bool, tag_name: TagName, union: crate::pattern::Union, }, EnumLiteral { tag_id: u8, tag_name: TagName, union: crate::pattern::Union, }, StrLiteral(Box), RecordDestructure(Vec<'a, RecordDestruct<'a>>, Layout<'a>), AppliedTag { tag_name: TagName, tag_id: u8, arguments: Vec<'a, (Pattern<'a>, Layout<'a>)>, layout: Layout<'a>, union: crate::pattern::Union, }, // Runtime Exceptions Shadowed(Region, Located), // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! UnsupportedPattern(Region), } #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct RecordDestruct<'a> { pub label: Lowercase, pub layout: Layout<'a>, pub symbol: Symbol, pub guard: Option>, } #[derive(Clone, Debug, PartialEq)] pub struct WhenBranch<'a> { pub patterns: Vec<'a, Pattern<'a>>, pub value: Expr<'a>, pub guard: Option>, } fn from_can_pattern<'a>( env: &mut Env<'a, '_>, can_pattern: &roc_can::pattern::Pattern, ) -> Pattern<'a> { use roc_can::pattern::Pattern::*; match can_pattern { Underscore => Pattern::Underscore, Identifier(symbol) => Pattern::Identifier(*symbol), IntLiteral(v) => Pattern::IntLiteral(*v), FloatLiteral(v) => Pattern::FloatLiteral(f64::to_bits(*v)), StrLiteral(v) => Pattern::StrLiteral(v.clone()), Shadowed(region, ident) => Pattern::Shadowed(*region, ident.clone()), UnsupportedPattern(region) => Pattern::UnsupportedPattern(*region), NumLiteral(var, num) => match num_argument_to_int_or_float(env.subs, *var) { IntOrFloat::IntType => Pattern::IntLiteral(*num), IntOrFloat::FloatType => Pattern::FloatLiteral(*num as u64), }, AppliedTag { whole_var, tag_name, arguments, .. } => { use crate::layout::UnionVariant::*; use crate::pattern::Union; let variant = crate::layout::union_sorted_tags(env.arena, *whole_var, env.subs, env.pointer_size); match variant { Never => unreachable!("there is no pattern of type `[]`"), Unit => Pattern::EnumLiteral { tag_id: 0, tag_name: tag_name.clone(), union: Union { render_as: RenderAs::Tag, alternatives: vec![Ctor { tag_id: TagId(0), name: tag_name.clone(), arity: 0, }], }, }, BoolUnion { ttrue, ffalse } => Pattern::BitLiteral { value: tag_name == &ttrue, tag_name: tag_name.clone(), union: Union { render_as: RenderAs::Tag, alternatives: vec![ Ctor { tag_id: TagId(0), name: ffalse, arity: 0, }, Ctor { tag_id: TagId(1), name: ttrue, arity: 0, }, ], }, }, ByteUnion(tag_names) => { let tag_id = tag_names .iter() .position(|key| key == tag_name) .expect("tag must be in its own type"); let mut ctors = std::vec::Vec::with_capacity(tag_names.len()); for (i, tag_name) in tag_names.iter().enumerate() { ctors.push(Ctor { tag_id: TagId(i as u8), name: tag_name.clone(), arity: 0, }) } let union = crate::pattern::Union { render_as: RenderAs::Tag, alternatives: ctors, }; Pattern::EnumLiteral { tag_id: tag_id as u8, tag_name: tag_name.clone(), union, } } Unwrapped(field_layouts) => { let union = crate::pattern::Union { render_as: RenderAs::Tag, alternatives: vec![Ctor { tag_id: TagId(0), name: tag_name.clone(), arity: field_layouts.len(), }], }; let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); for ((_, loc_pat), layout) in arguments.iter().zip(field_layouts.iter()) { mono_args.push((from_can_pattern(env, &loc_pat.value), layout.clone())); } let layout = Layout::Struct(field_layouts.into_bump_slice()); Pattern::AppliedTag { tag_name: tag_name.clone(), tag_id: 0, arguments: mono_args, union, layout, } } Wrapped(tags) => { let mut ctors = std::vec::Vec::with_capacity(tags.len()); for (i, (tag_name, args)) in tags.iter().enumerate() { ctors.push(Ctor { tag_id: TagId(i as u8), name: tag_name.clone(), // don't include tag discriminant in arity arity: args.len() - 1, }) } let union = crate::pattern::Union { render_as: RenderAs::Tag, alternatives: ctors, }; let (tag_id, (_, argument_layouts)) = tags .iter() .enumerate() .find(|(_, (key, _))| key == tag_name) .expect("tag must be in its own type"); let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); // disregard the tag discriminant layout let it = argument_layouts[1..].iter(); for ((_, loc_pat), layout) in arguments.iter().zip(it) { mono_args.push((from_can_pattern(env, &loc_pat.value), layout.clone())); } let mut layouts: Vec<&'a [Layout<'a>]> = Vec::with_capacity_in(tags.len(), env.arena); for (_, arg_layouts) in tags.into_iter() { layouts.push(arg_layouts); } let layout = Layout::Union(layouts.into_bump_slice()); Pattern::AppliedTag { tag_name: tag_name.clone(), tag_id: tag_id as u8, arguments: mono_args, union, layout, } } } } RecordDestructure { whole_var, destructs, .. } => { let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena); let mut destructs = destructs.clone(); destructs.sort_by(|a, b| a.value.label.cmp(&b.value.label)); let mut it = destructs.iter(); let mut opt_destruct = it.next(); let btree = crate::layout::record_fields_btree( env.arena, *whole_var, env.subs, env.pointer_size, ); let mut field_layouts = Vec::with_capacity_in(btree.len(), env.arena); for (label, field_layout) in btree.into_iter() { if let Some(destruct) = opt_destruct { if destruct.value.label == label { opt_destruct = it.next(); mono_destructs.push(from_can_record_destruct( env, &destruct.value, field_layout.clone(), )); } else { // insert underscore pattern mono_destructs.push(RecordDestruct { label: label.clone(), symbol: env.fresh_symbol(), layout: field_layout.clone(), guard: Some(Pattern::Underscore), }); } } else { // insert underscore pattern mono_destructs.push(RecordDestruct { label: label.clone(), symbol: env.fresh_symbol(), layout: field_layout.clone(), guard: Some(Pattern::Underscore), }); } field_layouts.push(field_layout); } Pattern::RecordDestructure( mono_destructs, Layout::Struct(field_layouts.into_bump_slice()), ) } } } fn from_can_record_destruct<'a>( env: &mut Env<'a, '_>, can_rd: &roc_can::pattern::RecordDestruct, field_layout: Layout<'a>, ) -> RecordDestruct<'a> { RecordDestruct { label: can_rd.label.clone(), symbol: can_rd.symbol, layout: field_layout, guard: match &can_rd.guard { None => None, Some((_, loc_pattern)) => Some(from_can_pattern(env, &loc_pattern.value)), }, } } pub fn specialize_equality<'a>( arena: &'a Bump, lhs: Expr<'a>, rhs: Expr<'a>, layout: Layout<'a>, ) -> Expr<'a> { let symbol = match &layout { Layout::Builtin(builtin) => match builtin { Builtin::Int64 => Symbol::INT_EQ_I64, Builtin::Float64 => Symbol::FLOAT_EQ, Builtin::Byte => Symbol::INT_EQ_I8, Builtin::Bool => Symbol::INT_EQ_I1, other => todo!("Cannot yet compare for equality {:?}", other), }, other => todo!("Cannot yet compare for equality {:?}", other), }; Expr::CallByName( symbol, arena.alloc([(lhs, layout.clone()), (rhs, layout.clone())]), ) }