From a1750567ff4420df56fce452423c03811f693653 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 30 Apr 2020 20:31:36 -0400 Subject: [PATCH 01/11] wip canonical builtins in modules --- cli/src/main.rs | 3 ++ compiler/can/src/builtins.rs | 60 ++++++++++++++++++++++++----------- compiler/load/src/file.rs | 12 ++++--- compiler/module/src/symbol.rs | 14 ++++++-- 4 files changed, 63 insertions(+), 26 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 0be3741de3..3a069935d1 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -307,12 +307,15 @@ fn gen( let loc_expr = main_expr.unwrap_or_else(|| { panic!("TODO gracefully handle the case where `main` was declared but not exposed") }); + let mut subs = loaded.solved.into_inner(); let content = match main_var { Some(var) => subs.get_without_compacting(var).content, None => todo!("TODO gracefully handle the case where `main` was declared but not exposed"), }; + todo!("step 1. mono-module, step 2. actually have main here use the loaded builtin modules!"); + // Generate the binary let context = Context::create(); diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 00d7266c9e..9339b28659 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1,13 +1,27 @@ -use crate::def::Def; -use crate::expr::Expr; -use crate::expr::Recursive; -use roc_collections::all::SendMap; +use crate::def::{Declaration, Def}; +use crate::expr::{Expr, Recursive}; +use roc_collections::all::{MutMap, MutSet, SendMap}; use roc_module::ident::TagName; -use roc_module::symbol::Symbol; +use roc_module::symbol::{ModuleId, Symbol}; use roc_parse::operator::CalledVia; use roc_region::all::{Located, Region}; use roc_types::subs::{VarStore, Variable}; +fn exposed_symbols() -> MutMap> { + let mut all = MutMap::default(); // TODO use with_capacity_and_hasher here + + all.insert(ModuleId::LIST, { + let mut set = MutSet::default(); // TODO use with_capacity_and_hasher here + + set.insert(Symbol::LIST_GET); + set.insert(Symbol::LIST_FIRST); + + set + }); + + all +} + /// Some builtins cannot be constructed in code gen alone, and need to be defined /// as separate Roc defs. For example, List.get has this type: /// @@ -25,16 +39,24 @@ use roc_types::subs::{VarStore, Variable}; /// delegates to the compiler-internal List.getUnsafe function to do the actual /// lookup (if the bounds check passed). That internal function is hardcoded in code gen, /// which works fine because it doesn't involve any open tag unions. -pub fn builtin_defs(var_store: &VarStore) -> Vec { - vec![ - list_get(var_store), - list_first(var_store), - int_div(var_store), - ] +pub fn declarations_by_id() -> MutMap> { + let var_store = VarStore::default(); + let mut decls = MutMap::default(); // TODO use with_capacity_and_hasher here + + decls.insert( + ModuleId::LIST, + vec![ + list_get(&var_store), + list_first(&var_store), + int_div(&var_store), + ], + ); + + decls } /// List.get : List elem, Int -> Result elem [ OutOfBounds ]* -fn list_get(var_store: &VarStore) -> Def { +fn list_get(var_store: &VarStore) -> Declaration { use crate::expr::Expr::*; defn( @@ -105,7 +127,7 @@ fn list_get(var_store: &VarStore) -> Def { } /// Int.div : Int, Int -> Result Int [ DivByZero ]* -fn int_div(var_store: &VarStore) -> Def { +fn int_div(var_store: &VarStore) -> Declaration { use crate::expr::Expr::*; use crate::pattern::Pattern::*; @@ -174,17 +196,17 @@ fn int_div(var_store: &VarStore) -> Def { Box::new((no_region(body), var_store.fresh())), ); - Def { + Declare(Def { loc_pattern: no_region(Identifier(Symbol::INT_DIV)), loc_expr: no_region(expr), expr_var: var_store.fresh(), pattern_vars: SendMap::default(), annotation: None, - } + }) } /// List.first : List elem -> Result elem [ ListWasEmpty ]* -fn list_first(var_store: &VarStore) -> Def { +fn list_first(var_store: &VarStore) -> Declaration { use crate::expr::Expr::*; defn( @@ -276,7 +298,7 @@ fn call(symbol: Symbol, args: Vec, var_store: &VarStore) -> Expr { } #[inline(always)] -fn defn(fn_name: Symbol, args: Vec, var_store: &VarStore, body: Expr) -> Def { +fn defn(fn_name: Symbol, args: Vec, var_store: &VarStore, body: Expr) -> Declaration { use crate::expr::Expr::*; use crate::pattern::Pattern::*; @@ -293,11 +315,11 @@ fn defn(fn_name: Symbol, args: Vec, var_store: &VarStore, body: Expr) -> Box::new((no_region(body), var_store.fresh())), ); - Def { + Declaration::Declare(Def { loc_pattern: no_region(Identifier(fn_name)), loc_expr: no_region(expr), expr_var: var_store.fresh(), pattern_vars: SendMap::default(), annotation: None, - } + }) } diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 6b7687f70c..69cc3d2539 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -193,8 +193,7 @@ pub async fn load<'a>( loading_started.insert(root_id); // The declarations we'll ultimately be returning - let mut declarations_by_id: MutMap> = MutMap::default(); - + let mut declarations_by_id: MutMap> = roc_can::builtins::declarations_by_id(); let mut exposed_symbols_by_module: MutMap> = MutMap::default(); // Modules which are waiting for certain headers to be parsed @@ -469,8 +468,11 @@ pub async fn load<'a>( stdlib, ); - for _unused_module_id in unused_modules.iter() { - panic!("TODO gracefully report unused imports for {:?}, namely {:?}", home, unused_modules); + for unused_module_id in unused_modules.iter() { + // Never report builtin modules as unused + if !unused_module_id.is_builtin() { + todo!("TODO gracefully report unused imports for {:?}, namely {:?}", home, unused_modules); + } } } } @@ -581,7 +583,7 @@ fn send_header<'a>( // If it isn't, report a problem! let mut imported: Vec<(ModuleName, Vec, Region)> = Vec::with_capacity(imports.len()); - let mut imported_modules: MutSet = MutSet::default(); + let mut imported_modules: MutSet = ModuleId::default_imports(); let mut scope_size = 0; for loc_entry in imports { diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index bb2c11ff2d..1d270d6b79 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -1,8 +1,8 @@ use crate::ident::Ident; use inlinable_string::InlinableString; -use roc_collections::all::{default_hasher, ImMap, MutMap}; +use roc_collections::all::{default_hasher, ImMap, MutMap, MutSet}; use roc_region::all::Region; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::{fmt, u32}; // TODO: benchmark this as { ident_id: u32, module_id: u32 } and see if perf stays the same @@ -496,6 +496,16 @@ macro_rules! define_builtins { self.0 < $total } + pub fn default_imports() -> MutSet { + let mut all = HashSet::with_capacity_and_hasher($total, default_hasher()); + + $( + all.insert(ModuleId($module_id)); + )+ + + all + } + $( pub const $module_const: ModuleId = ModuleId($module_id); )+ From 072cf4e84467dc1b067420792e9314f73e2cd54a Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 30 Apr 2020 20:31:45 -0400 Subject: [PATCH 02/11] Revert "wip canonical builtins in modules" This reverts commit e1935a1e9f0c7ea1ffefe5f2f4f07f2c7666363b. --- cli/src/main.rs | 3 -- compiler/can/src/builtins.rs | 60 +++++++++++------------------------ compiler/load/src/file.rs | 12 +++---- compiler/module/src/symbol.rs | 14 ++------ 4 files changed, 26 insertions(+), 63 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 3a069935d1..0be3741de3 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -307,15 +307,12 @@ fn gen( let loc_expr = main_expr.unwrap_or_else(|| { panic!("TODO gracefully handle the case where `main` was declared but not exposed") }); - let mut subs = loaded.solved.into_inner(); let content = match main_var { Some(var) => subs.get_without_compacting(var).content, None => todo!("TODO gracefully handle the case where `main` was declared but not exposed"), }; - todo!("step 1. mono-module, step 2. actually have main here use the loaded builtin modules!"); - // Generate the binary let context = Context::create(); diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 9339b28659..00d7266c9e 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1,27 +1,13 @@ -use crate::def::{Declaration, Def}; -use crate::expr::{Expr, Recursive}; -use roc_collections::all::{MutMap, MutSet, SendMap}; +use crate::def::Def; +use crate::expr::Expr; +use crate::expr::Recursive; +use roc_collections::all::SendMap; use roc_module::ident::TagName; -use roc_module::symbol::{ModuleId, Symbol}; +use roc_module::symbol::Symbol; use roc_parse::operator::CalledVia; use roc_region::all::{Located, Region}; use roc_types::subs::{VarStore, Variable}; -fn exposed_symbols() -> MutMap> { - let mut all = MutMap::default(); // TODO use with_capacity_and_hasher here - - all.insert(ModuleId::LIST, { - let mut set = MutSet::default(); // TODO use with_capacity_and_hasher here - - set.insert(Symbol::LIST_GET); - set.insert(Symbol::LIST_FIRST); - - set - }); - - all -} - /// Some builtins cannot be constructed in code gen alone, and need to be defined /// as separate Roc defs. For example, List.get has this type: /// @@ -39,24 +25,16 @@ fn exposed_symbols() -> MutMap> { /// delegates to the compiler-internal List.getUnsafe function to do the actual /// lookup (if the bounds check passed). That internal function is hardcoded in code gen, /// which works fine because it doesn't involve any open tag unions. -pub fn declarations_by_id() -> MutMap> { - let var_store = VarStore::default(); - let mut decls = MutMap::default(); // TODO use with_capacity_and_hasher here - - decls.insert( - ModuleId::LIST, - vec![ - list_get(&var_store), - list_first(&var_store), - int_div(&var_store), - ], - ); - - decls +pub fn builtin_defs(var_store: &VarStore) -> Vec { + vec![ + list_get(var_store), + list_first(var_store), + int_div(var_store), + ] } /// List.get : List elem, Int -> Result elem [ OutOfBounds ]* -fn list_get(var_store: &VarStore) -> Declaration { +fn list_get(var_store: &VarStore) -> Def { use crate::expr::Expr::*; defn( @@ -127,7 +105,7 @@ fn list_get(var_store: &VarStore) -> Declaration { } /// Int.div : Int, Int -> Result Int [ DivByZero ]* -fn int_div(var_store: &VarStore) -> Declaration { +fn int_div(var_store: &VarStore) -> Def { use crate::expr::Expr::*; use crate::pattern::Pattern::*; @@ -196,17 +174,17 @@ fn int_div(var_store: &VarStore) -> Declaration { Box::new((no_region(body), var_store.fresh())), ); - Declare(Def { + Def { loc_pattern: no_region(Identifier(Symbol::INT_DIV)), loc_expr: no_region(expr), expr_var: var_store.fresh(), pattern_vars: SendMap::default(), annotation: None, - }) + } } /// List.first : List elem -> Result elem [ ListWasEmpty ]* -fn list_first(var_store: &VarStore) -> Declaration { +fn list_first(var_store: &VarStore) -> Def { use crate::expr::Expr::*; defn( @@ -298,7 +276,7 @@ fn call(symbol: Symbol, args: Vec, var_store: &VarStore) -> Expr { } #[inline(always)] -fn defn(fn_name: Symbol, args: Vec, var_store: &VarStore, body: Expr) -> Declaration { +fn defn(fn_name: Symbol, args: Vec, var_store: &VarStore, body: Expr) -> Def { use crate::expr::Expr::*; use crate::pattern::Pattern::*; @@ -315,11 +293,11 @@ fn defn(fn_name: Symbol, args: Vec, var_store: &VarStore, body: Expr) -> Box::new((no_region(body), var_store.fresh())), ); - Declaration::Declare(Def { + Def { loc_pattern: no_region(Identifier(fn_name)), loc_expr: no_region(expr), expr_var: var_store.fresh(), pattern_vars: SendMap::default(), annotation: None, - }) + } } diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 69cc3d2539..6b7687f70c 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -193,7 +193,8 @@ pub async fn load<'a>( loading_started.insert(root_id); // The declarations we'll ultimately be returning - let mut declarations_by_id: MutMap> = roc_can::builtins::declarations_by_id(); + let mut declarations_by_id: MutMap> = MutMap::default(); + let mut exposed_symbols_by_module: MutMap> = MutMap::default(); // Modules which are waiting for certain headers to be parsed @@ -468,11 +469,8 @@ pub async fn load<'a>( stdlib, ); - for unused_module_id in unused_modules.iter() { - // Never report builtin modules as unused - if !unused_module_id.is_builtin() { - todo!("TODO gracefully report unused imports for {:?}, namely {:?}", home, unused_modules); - } + for _unused_module_id in unused_modules.iter() { + panic!("TODO gracefully report unused imports for {:?}, namely {:?}", home, unused_modules); } } } @@ -583,7 +581,7 @@ fn send_header<'a>( // If it isn't, report a problem! let mut imported: Vec<(ModuleName, Vec, Region)> = Vec::with_capacity(imports.len()); - let mut imported_modules: MutSet = ModuleId::default_imports(); + let mut imported_modules: MutSet = MutSet::default(); let mut scope_size = 0; for loc_entry in imports { diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 1d270d6b79..bb2c11ff2d 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -1,8 +1,8 @@ use crate::ident::Ident; use inlinable_string::InlinableString; -use roc_collections::all::{default_hasher, ImMap, MutMap, MutSet}; +use roc_collections::all::{default_hasher, ImMap, MutMap}; use roc_region::all::Region; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::{fmt, u32}; // TODO: benchmark this as { ident_id: u32, module_id: u32 } and see if perf stays the same @@ -496,16 +496,6 @@ macro_rules! define_builtins { self.0 < $total } - pub fn default_imports() -> MutSet { - let mut all = HashSet::with_capacity_and_hasher($total, default_hasher()); - - $( - all.insert(ModuleId($module_id)); - )+ - - all - } - $( pub const $module_const: ModuleId = ModuleId($module_id); )+ From 5c3136907210c62b2a51c368895ad3575c9d5a7d Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 30 Apr 2020 20:33:02 -0400 Subject: [PATCH 03/11] Code gen thunks for top-level module decls --- cli/src/main.rs | 58 ++++++++++++++++++++++++--- compiler/load/src/file.rs | 8 +--- compiler/load/tests/test_load.rs | 58 ++++++++++++++------------- compiler/load/tests/test_uniq_load.rs | 58 ++++++++++++++------------- compiler/mono/src/expr.rs | 16 +++++++- examples/hello-world/Hello.roc | 7 +++- 6 files changed, 137 insertions(+), 68 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 0be3741de3..43335c9af9 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -16,7 +16,7 @@ use roc_gen::llvm::build::{ use roc_gen::llvm::convert::basic_type_from_layout; use roc_load::file::{LoadedModule, LoadingProblem}; use roc_module::symbol::Symbol; -use roc_mono::expr::{Expr, Procs}; +use roc_mono::expr::{Expr, PartialProc, Procs}; use roc_mono::layout::Layout; use std::time::SystemTime; @@ -275,17 +275,22 @@ fn gen( } } + let mut decls_by_id = loaded.declarations_by_id; + let home_decls = decls_by_id + .remove(&loaded.module_id) + .expect("Root module ID not found in loaded declarations_by_id"); + // We use a loop label here so we can break all the way out of a nested // loop inside DeclareRec if we find the expr there. // // https://doc.rust-lang.org/1.30.0/book/first-edition/loops.html#loop-labels - 'find_expr: for decl in loaded.declarations { + 'find_expr: for decl in home_decls.iter() { use roc_can::def::Declaration::*; match decl { Declare(def) => { if def.pattern_vars.contains_key(&main_symbol) { - main_expr = Some(def.loc_expr); + main_expr = Some(def.loc_expr.clone()); break 'find_expr; } @@ -294,7 +299,7 @@ fn gen( DeclareRec(defs) => { for def in defs { if def.pattern_vars.contains_key(&main_symbol) { - main_expr = Some(def.loc_expr); + main_expr = Some(def.loc_expr.clone()); break 'find_expr; } @@ -347,9 +352,52 @@ fn gen( ptr_bytes, }; let mut procs = Procs::default(); + + // TODO remove this + decls_by_id.insert(loaded.module_id, home_decls); + + // Populate Procs from decls + for (_, mut decls) in decls_by_id.drain() { + for decl in decls.drain(..) { + use roc_can::def::Declaration::*; + use roc_can::expr::Expr::*; + use roc_can::pattern::Pattern::*; + + match decl { + Declare(def) => match def.loc_pattern.value { + Identifier(symbol) => { + let proc = match def.loc_expr.value { + Closure(_, _, _, _, _) => { + todo!("TODO register PartialProc for Closure"); + } + body => PartialProc { + annotation: def.expr_var, + // This is a 0-arity thunk, so it has no arguments. + patterns: bumpalo::collections::Vec::new_in(arena), + body, + }, + }; + + procs.user_defined.insert(symbol, proc); + procs.module_decls.insert(symbol); + } + other => { + todo!("TODO gracefully handle Declare({:?})", other); + } + }, + DeclareRec(_defs) => { + todo!("TODO support DeclareRec"); + } + InvalidCycle(_loc_idents, _regions) => { + todo!("TODO handle InvalidCycle"); + } + } + } + } + let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); - // Populate Procs and get the low-level Expr from the canonical Expr + // Populate Procs further and get the low-level Expr from the canonical Expr let mut mono_problems = std::vec::Vec::new(); let main_body = Expr::new( &arena, diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 6b7687f70c..659bb90e04 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -53,7 +53,7 @@ pub struct LoadedModule { pub solved: Solved, pub can_problems: Vec, pub type_problems: Vec, - pub declarations: Vec, + pub declarations_by_id: MutMap>, pub exposed_vars_by_symbol: Vec<(Symbol, Variable)>, pub src: Box, } @@ -417,10 +417,6 @@ pub async fn load<'a>( .into_inner() .expect("Unwrapping mutex for module_ids"); - let declarations = declarations_by_id - .remove(&module_id) - .expect("declarations_by_id was missing root module_id entry"); - let interns = Interns { module_ids, all_ident_ids: constrained_ident_ids, @@ -432,7 +428,7 @@ pub async fn load<'a>( solved, can_problems, type_problems, - declarations, + declarations_by_id, exposed_vars_by_symbol, src, }); diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index 058673bd7a..8fca6d5d75 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -88,32 +88,36 @@ mod test_load { assert_eq!(loaded_module.can_problems, Vec::new()); assert_eq!(loaded_module.type_problems, Vec::new()); - let num_decls = loaded_module.declarations.len(); + let mut num_decls = 0; - for decl in loaded_module.declarations { - match decl { - Declare(def) => expect_def( - &loaded_module.interns, - &mut subs, - home, - &def, - &expected_types, - ), - DeclareRec(defs) => { - for def in defs { - expect_def( - &loaded_module.interns, - &mut subs, - home, - &def, - &expected_types, - ); + for decls in loaded_module.declarations_by_id.values() { + for decl in decls { + num_decls += 1; + + match decl { + Declare(def) => expect_def( + &loaded_module.interns, + &mut subs, + home, + &def, + &expected_types, + ), + DeclareRec(defs) => { + for def in defs { + expect_def( + &loaded_module.interns, + &mut subs, + home, + &def, + &expected_types, + ); + } } - } - cycle @ InvalidCycle(_, _) => { - panic!("Unexpected cyclic def in module declarations: {:?}", cycle); - } - }; + cycle @ InvalidCycle(_, _) => { + panic!("Unexpected cyclic def in module declarations: {:?}", cycle); + } + }; + } } assert_eq!(expected_types.len(), num_decls); @@ -141,9 +145,9 @@ mod test_load { assert_eq!(loaded_module.type_problems, Vec::new()); let def_count: usize = loaded_module - .declarations - .iter() - .map(|decl| decl.def_count()) + .declarations_by_id + .values() + .map(|decls| decls.iter().fold(0, |sum, decl| sum + decl.def_count())) .sum(); let expected_name = loaded_module diff --git a/compiler/load/tests/test_uniq_load.rs b/compiler/load/tests/test_uniq_load.rs index 785b98ebe1..15aed1ddd6 100644 --- a/compiler/load/tests/test_uniq_load.rs +++ b/compiler/load/tests/test_uniq_load.rs @@ -83,32 +83,36 @@ mod test_uniq_load { assert_eq!(loaded_module.can_problems, Vec::new()); assert_eq!(loaded_module.type_problems, Vec::new()); - let num_decls = loaded_module.declarations.len(); + let mut num_decls = 0; - for decl in loaded_module.declarations { - match decl { - Declare(def) => expect_def( - &loaded_module.interns, - &mut subs, - home, - &def, - &expected_types, - ), - DeclareRec(defs) => { - for def in defs { - expect_def( - &loaded_module.interns, - &mut subs, - home, - &def, - &expected_types, - ); + for decls in loaded_module.declarations_by_id.values() { + for decl in decls { + num_decls += 1; + + match decl { + Declare(def) => expect_def( + &loaded_module.interns, + &mut subs, + home, + &def, + &expected_types, + ), + DeclareRec(defs) => { + for def in defs { + expect_def( + &loaded_module.interns, + &mut subs, + home, + &def, + &expected_types, + ); + } } - } - cycle @ InvalidCycle(_, _) => { - panic!("Unexpected cyclic def in module declarations: {:?}", cycle); - } - }; + cycle @ InvalidCycle(_, _) => { + panic!("Unexpected cyclic def in module declarations: {:?}", cycle); + } + }; + } } assert_eq!(expected_types.len(), num_decls); @@ -136,9 +140,9 @@ mod test_uniq_load { assert_eq!(loaded_module.type_problems, Vec::new()); let def_count: usize = loaded_module - .declarations - .iter() - .map(|decl| decl.def_count()) + .declarations_by_id + .values() + .map(|decls| decls.iter().fold(0, |sum, decl| sum + decl.def_count())) .sum(); let expected_name = loaded_module diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 62734a1f2c..ebd6fd7825 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -27,7 +27,8 @@ pub struct Proc<'a> { #[derive(Clone, Debug, PartialEq, Default)] pub struct Procs<'a> { - user_defined: MutMap>, + pub user_defined: MutMap>, + pub module_decls: MutSet, anonymous: MutMap>>, specializations: MutMap>)>, builtin: MutSet, @@ -426,7 +427,18 @@ fn from_can<'a>( Int(_, num) => Expr::Int(num), Float(_, num) => Expr::Float(num), Str(string) | BlockStr(string) => Expr::Str(env.arena.alloc(string)), - Var(symbol) => Expr::Load(symbol), + Var(symbol) => { + if procs.module_decls.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), diff --git a/examples/hello-world/Hello.roc b/examples/hello-world/Hello.roc index ef6ba9f212..0d3d072597 100644 --- a/examples/hello-world/Hello.roc +++ b/examples/hello-world/Hello.roc @@ -1,3 +1,8 @@ app Hello provides [ main ] imports [] -main = "Hello, World!" +greeting = + hi = "Hello, World!" + + hi + +main = greeting From 1a2367c8aa33f4aedce47a103e8be60c2e20540f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 30 Apr 2020 20:49:18 -0400 Subject: [PATCH 04/11] appease clippy --- cli/src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli/src/main.rs b/cli/src/main.rs index 43335c9af9..1538d29d8b 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -220,6 +220,8 @@ async fn build_file( Ok(binary_path) } +// TODO this should probably use more helper functions +#[allow(clippy::cognitive_complexity)] fn gen( arena: &Bump, loaded: LoadedModule, From ee31eac0c5c49ec9a33dd80e1752b6447fc0500c Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 1 May 2020 17:53:32 -0400 Subject: [PATCH 05/11] Code gen toplevel module functions --- cli/src/main.rs | 65 ++++++---- cli/src/repl.rs | 19 +-- compiler/gen/tests/helpers/eval.rs | 36 +++++- compiler/mono/src/expr.rs | 136 ++++++++++----------- compiler/mono/tests/test_mono.rs | 17 +-- compiler/mono/tests/test_opt.rs | 34 +++--- compiler/reporting/tests/test_reporting.rs | 17 +-- 7 files changed, 183 insertions(+), 141 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 1538d29d8b..6f2850b1e0 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -16,7 +16,7 @@ use roc_gen::llvm::build::{ use roc_gen::llvm::convert::basic_type_from_layout; use roc_load::file::{LoadedModule, LoadingProblem}; use roc_module::symbol::Symbol; -use roc_mono::expr::{Expr, PartialProc, Procs}; +use roc_mono::expr::{Env, Expr, PartialProc, Procs}; use roc_mono::layout::Layout; use std::time::SystemTime; @@ -354,6 +354,18 @@ fn gen( ptr_bytes, }; 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, + problems: &mut mono_problems, + home, + ident_ids: &mut ident_ids, + pointer_size: ptr_bytes, + symbol_counter: 0, + jump_counter: arena.alloc(0), + }; // TODO remove this decls_by_id.insert(loaded.module_id, home_decls); @@ -368,20 +380,31 @@ fn gen( match decl { Declare(def) => match def.loc_pattern.value { Identifier(symbol) => { - let proc = match def.loc_expr.value { - Closure(_, _, _, _, _) => { - todo!("TODO register PartialProc for Closure"); - } - body => PartialProc { - annotation: def.expr_var, - // This is a 0-arity thunk, so it has no arguments. - patterns: bumpalo::collections::Vec::new_in(arena), - body, - }, - }; + match def.loc_expr.value { + Closure(annotation, _, _, loc_args, boxed_body) => { + let (loc_body, ret_var) = *boxed_body; - procs.user_defined.insert(symbol, proc); - procs.module_decls.insert(symbol); + procs.insert_closure( + &mut mono_env, + Some(symbol), + annotation, + loc_args, + loc_body, + ret_var, + ); + } + body => { + let proc = PartialProc { + annotation: def.expr_var, + // This is a 0-arity thunk, so it has no arguments. + patterns: bumpalo::collections::Vec::new_in(arena), + body, + }; + + procs.user_defined.insert(symbol, proc); + procs.module_thunks.insert(symbol); + } + }; } other => { todo!("TODO gracefully handle Declare({:?})", other); @@ -397,20 +420,8 @@ fn gen( } } - let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); - // Populate Procs further and get the low-level Expr from the canonical Expr - let mut mono_problems = std::vec::Vec::new(); - let main_body = Expr::new( - &arena, - &mut subs, - &mut mono_problems, - loc_expr.value, - &mut procs, - home, - &mut ident_ids, - ptr_bytes, - ); + let main_body = Expr::new(&mut mono_env, loc_expr.value, &mut procs); // Put this module's ident_ids back in the interns, so we can use them in env. env.interns.all_ident_ids.insert(home, ident_ids); diff --git a/cli/src/repl.rs b/cli/src/repl.rs index ce6df93395..6f3c072b49 100644 --- a/cli/src/repl.rs +++ b/cli/src/repl.rs @@ -246,16 +246,17 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St // Populate Procs and get the low-level Expr from the canonical Expr let mut mono_problems = Vec::new(); - let main_body = roc_mono::expr::Expr::new( - &arena, - &mut subs, - &mut mono_problems, - loc_expr.value, - &mut procs, + let mut mono_env = roc_mono::expr::Env { + arena: &arena, + subs: &mut subs, + problems: &mut mono_problems, home, - &mut ident_ids, - ptr_bytes, - ); + ident_ids: &mut ident_ids, + pointer_size: ptr_bytes, + symbol_counter: 0, + jump_counter: arena.alloc(0), + }; + let main_body = roc_mono::expr::Expr::new(&mut mono_env, loc_expr.value, &mut procs); // Put this module's ident_ids back in the interns, so we can use them in Env. env.interns.all_ident_ids.insert(home, ident_ids); diff --git a/compiler/gen/tests/helpers/eval.rs b/compiler/gen/tests/helpers/eval.rs index 34f9dd5b44..69208d006c 100644 --- a/compiler/gen/tests/helpers/eval.rs +++ b/compiler/gen/tests/helpers/eval.rs @@ -63,7 +63,17 @@ macro_rules! assert_llvm_evals_to { // Populate Procs and get the low-level Expr from the canonical Expr let mut mono_problems = Vec::new(); - let main_body = Expr::new(&arena, &mut subs, &mut mono_problems, loc_expr.value, &mut procs, home, &mut ident_ids, ptr_bytes); + let mut mono_env = roc_mono::expr::Env { + arena: &arena, + subs: &mut subs, + problems: &mut mono_problems, + home, + ident_ids: &mut ident_ids, + pointer_size: ptr_bytes, + symbol_counter: 0, + jump_counter: arena.alloc(0), + }; + let main_body = Expr::new(&mut mono_env, loc_expr.value, &mut procs); // Put this module's ident_ids back in the interns, so we can use them in Env. env.interns.all_ident_ids.insert(home, ident_ids); @@ -215,7 +225,17 @@ macro_rules! assert_opt_evals_to { // Populate Procs and get the low-level Expr from the canonical Expr let mut mono_problems = Vec::new(); - let main_body = Expr::new(&arena, &mut subs, &mut mono_problems, loc_expr.value, &mut procs, home, &mut ident_ids, ptr_bytes); + let mut mono_env = roc_mono::expr::Env { + arena: &arena, + subs: &mut subs, + problems: &mut mono_problems, + home, + ident_ids: &mut ident_ids, + pointer_size: ptr_bytes, + symbol_counter: 0, + jump_counter: arena.alloc(0), + }; + let main_body = Expr::new(&mut mono_env, loc_expr.value, &mut procs); // Put this module's ident_ids back in the interns, so we can use them in Env. env.interns.all_ident_ids.insert(home, ident_ids); @@ -361,7 +381,17 @@ macro_rules! emit_expr { let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); // Populate Procs and get the low-level Expr from the canonical Expr - let main_body = Expr::new(&arena, &mut subs, loc_expr.value, &mut procs, home, &mut ident_ids, $crate::helpers::eval::POINTER_SIZE); + let mut mono_env = roc_mono::expr::Env { + arena: &arena, + subs: &mut subs, + problems: &mut mono_problems, + home, + ident_ids: &mut ident_ids, + pointer_size: ptr_bytes, + symbol_counter: 0, + jump_counter: arena.alloc(0), + }; + let main_body = Expr::new(&mut mono_env, loc_expr.value, &mut procs); // Put this module's ident_ids back in the interns, so we can use them in Env. env.interns.all_ident_ids.insert(home, ident_ids); diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index ebd6fd7825..e460d6434e 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -28,7 +28,7 @@ pub struct Proc<'a> { #[derive(Clone, Debug, PartialEq, Default)] pub struct Procs<'a> { pub user_defined: MutMap>, - pub module_decls: MutSet, + pub module_thunks: MutSet, anonymous: MutMap>>, specializations: MutMap>)>, builtin: MutSet, @@ -43,6 +43,66 @@ impl<'a> Procs<'a> { 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, @@ -97,8 +157,8 @@ pub struct Env<'a, 'i> { pub home: ModuleId, pub ident_ids: &'i mut IdentIds, pub pointer_size: u32, - symbol_counter: usize, pub jump_counter: &'a mut u64, + pub symbol_counter: usize, } impl<'a, 'i> Env<'a, 'i> { @@ -204,27 +264,11 @@ pub enum MonoProblem { #[allow(clippy::too_many_arguments)] impl<'a> Expr<'a> { pub fn new( - arena: &'a Bump, - subs: &'a mut Subs, - problems: &mut std::vec::Vec, + env: &mut Env<'a, '_>, can_expr: roc_can::expr::Expr, procs: &mut Procs<'a>, - home: ModuleId, - ident_ids: &mut IdentIds, - pointer_size: u32, ) -> Self { - let mut env = Env { - arena, - subs, - problems, - home, - ident_ids, - pointer_size, - symbol_counter: 0, - jump_counter: arena.alloc(0), - }; - - from_can(&mut env, can_expr, procs, None) + from_can(env, can_expr, procs, None) } } @@ -428,7 +472,7 @@ fn from_can<'a>( Float(_, num) => Expr::Float(num), Str(string) | BlockStr(string) => Expr::Str(env.arena.alloc(string)), Var(symbol) => { - if procs.module_decls.contains(&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; @@ -444,55 +488,7 @@ fn from_can<'a>( Closure(annotation, _, _, loc_args, boxed_body) => { let (loc_body, ret_var) = *boxed_body; - - // 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); - - let symbol = match name { - Some(symbol) => { - // a named closure - procs.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, - procs, - annotation, - ret_var, - symbol, - &arg_vars, - &arg_symbols, - annotation, - body.value, - ) - .ok(); - - procs.insert_anonymous(symbol, opt_proc); - - symbol - } - }; + let symbol = procs.insert_closure(env, name, annotation, loc_args, loc_body, ret_var); Expr::FunctionPointer(symbol) } diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 8269fb347c..6c46c5639f 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -53,16 +53,17 @@ mod test_mono { // Populate Procs and Subs, and get the low-level Expr from the canonical Expr let mut mono_problems = Vec::new(); - let mono_expr = Expr::new( - &arena, - &mut subs, - &mut mono_problems, - loc_expr.value, - &mut procs, + let mut mono_env = roc_mono::expr::Env { + arena: &arena, + subs: &mut subs, + problems: &mut mono_problems, home, - &mut ident_ids, + ident_ids: &mut ident_ids, pointer_size, - ); + symbol_counter: 0, + jump_counter: arena.alloc(0), + }; + let mono_expr = Expr::new(&mut mono_env, loc_expr.value, &mut procs); // Put this module's ident_ids back in the interns interns.all_ident_ids.insert(home, ident_ids); diff --git a/compiler/mono/tests/test_opt.rs b/compiler/mono/tests/test_opt.rs index 6cdde4c792..b93aff0e58 100644 --- a/compiler/mono/tests/test_opt.rs +++ b/compiler/mono/tests/test_opt.rs @@ -42,16 +42,17 @@ mod test_opt { // Populate Procs and Subs, and get the low-level Expr from the canonical Expr let mut mono_problems = Vec::new(); - let mono_expr = Expr::new( - &arena, - &mut subs, - &mut mono_problems, - loc_expr.value, - &mut procs, + let mut mono_env = roc_mono::expr::Env { + arena: &arena, + subs: &mut subs, + problems: &mut mono_problems, home, - &mut ident_ids, + ident_ids: &mut ident_ids, pointer_size, - ); + symbol_counter: 0, + jump_counter: arena.alloc(0), + }; + let mono_expr = Expr::new(&mut mono_env, loc_expr.value, &mut procs); let unexpected_calls = extract_named_calls(&mono_expr, &mut calls); let expected = CallProblems::default(); @@ -193,16 +194,17 @@ mod test_opt { // Populate Procs and Subs, and get the low-level Expr from the canonical Expr let mut mono_problems = Vec::new(); - let mono_expr = Expr::new( - &arena, - &mut subs, - &mut mono_problems, - loc_expr.value, - &mut procs, + let mut mono_env = roc_mono::expr::Env { + arena: &arena, + subs: &mut subs, + problems: &mut mono_problems, home, - &mut ident_ids, + ident_ids: &mut ident_ids, pointer_size, - ); + symbol_counter: 0, + jump_counter: arena.alloc(0), + }; + let mono_expr = Expr::new(&mut mono_env, loc_expr.value, &mut procs); assert_eq!(mono_expr, expected); } diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index 23225fffef..7b0bc1bc03 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -90,16 +90,17 @@ mod test_reporting { let pointer_size = std::mem::size_of::() as u32; // Populate Procs and Subs, and get the low-level Expr from the canonical Expr - let _mono_expr = Expr::new( - &arena, - &mut subs, - &mut mono_problems, - loc_expr.value, - &mut procs, + let mut mono_env = roc_mono::expr::Env { + arena: &arena, + subs: &mut subs, + problems: &mut mono_problems, home, - &mut ident_ids, + ident_ids: &mut ident_ids, pointer_size, - ); + symbol_counter: 0, + jump_counter: arena.alloc(0), + }; + let _mono_expr = Expr::new(&mut mono_env, loc_expr.value, &mut procs); } Ok((unify_problems, can_problems, mono_problems, home, interns)) From 5f330737039afd2b1c8964802e7a708ed296a6af Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 1 May 2020 18:08:26 -0400 Subject: [PATCH 06/11] fix tests --- compiler/load/tests/test_load.rs | 61 ++++++++++++++------------- compiler/load/tests/test_uniq_load.rs | 61 ++++++++++++++------------- 2 files changed, 62 insertions(+), 60 deletions(-) diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index 8fca6d5d75..b54c1fd199 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -81,7 +81,7 @@ mod test_load { } } - fn expect_types(loaded_module: LoadedModule, expected_types: HashMap<&str, &str>) { + fn expect_types(mut loaded_module: LoadedModule, expected_types: HashMap<&str, &str>) { let home = loaded_module.module_id; let mut subs = loaded_module.solved.into_inner(); @@ -90,34 +90,32 @@ mod test_load { let mut num_decls = 0; - for decls in loaded_module.declarations_by_id.values() { - for decl in decls { - num_decls += 1; + for decl in loaded_module.declarations_by_id.remove(&home).unwrap() { + num_decls += 1; - match decl { - Declare(def) => expect_def( - &loaded_module.interns, - &mut subs, - home, - &def, - &expected_types, - ), - DeclareRec(defs) => { - for def in defs { - expect_def( - &loaded_module.interns, - &mut subs, - home, - &def, - &expected_types, - ); - } + match decl { + Declare(def) => expect_def( + &loaded_module.interns, + &mut subs, + home, + &def, + &expected_types, + ), + DeclareRec(defs) => { + for def in defs { + expect_def( + &loaded_module.interns, + &mut subs, + home, + &def, + &expected_types, + ); } - cycle @ InvalidCycle(_, _) => { - panic!("Unexpected cyclic def in module declarations: {:?}", cycle); - } - }; - } + } + cycle @ InvalidCycle(_, _) => { + panic!("Unexpected cyclic def in module declarations: {:?}", cycle); + } + }; } assert_eq!(expected_types.len(), num_decls); @@ -139,15 +137,18 @@ mod test_load { subs_by_module, ) .await; - let loaded_module = loaded.expect("Test module failed to load"); + + let mut loaded_module = loaded.expect("Test module failed to load"); assert_eq!(loaded_module.can_problems, Vec::new()); assert_eq!(loaded_module.type_problems, Vec::new()); let def_count: usize = loaded_module .declarations_by_id - .values() - .map(|decls| decls.iter().fold(0, |sum, decl| sum + decl.def_count())) + .remove(&loaded_module.module_id) + .unwrap() + .into_iter() + .map(|decl| decl.def_count()) .sum(); let expected_name = loaded_module diff --git a/compiler/load/tests/test_uniq_load.rs b/compiler/load/tests/test_uniq_load.rs index 15aed1ddd6..ee3360632e 100644 --- a/compiler/load/tests/test_uniq_load.rs +++ b/compiler/load/tests/test_uniq_load.rs @@ -76,7 +76,7 @@ mod test_uniq_load { } } - fn expect_types(loaded_module: LoadedModule, expected_types: HashMap<&str, &str>) { + fn expect_types(mut loaded_module: LoadedModule, expected_types: HashMap<&str, &str>) { let home = loaded_module.module_id; let mut subs = loaded_module.solved.into_inner(); @@ -85,34 +85,32 @@ mod test_uniq_load { let mut num_decls = 0; - for decls in loaded_module.declarations_by_id.values() { - for decl in decls { - num_decls += 1; + for decl in loaded_module.declarations_by_id.remove(&home).unwrap() { + num_decls += 1; - match decl { - Declare(def) => expect_def( - &loaded_module.interns, - &mut subs, - home, - &def, - &expected_types, - ), - DeclareRec(defs) => { - for def in defs { - expect_def( - &loaded_module.interns, - &mut subs, - home, - &def, - &expected_types, - ); - } + match decl { + Declare(def) => expect_def( + &loaded_module.interns, + &mut subs, + home, + &def, + &expected_types, + ), + DeclareRec(defs) => { + for def in defs { + expect_def( + &loaded_module.interns, + &mut subs, + home, + &def, + &expected_types, + ); } - cycle @ InvalidCycle(_, _) => { - panic!("Unexpected cyclic def in module declarations: {:?}", cycle); - } - }; - } + } + cycle @ InvalidCycle(_, _) => { + panic!("Unexpected cyclic def in module declarations: {:?}", cycle); + } + }; } assert_eq!(expected_types.len(), num_decls); @@ -134,15 +132,18 @@ mod test_uniq_load { subs_by_module, ) .await; - let loaded_module = loaded.expect("Test module failed to load"); + + let mut loaded_module = loaded.expect("Test module failed to load"); assert_eq!(loaded_module.can_problems, Vec::new()); assert_eq!(loaded_module.type_problems, Vec::new()); let def_count: usize = loaded_module .declarations_by_id - .values() - .map(|decls| decls.iter().fold(0, |sum, decl| sum + decl.def_count())) + .remove(&loaded_module.module_id) + .unwrap() + .into_iter() + .map(|decl| decl.def_count()) .sum(); let expected_name = loaded_module From 095844c2c188c7debcf0971d5cb87157c5a5cfd6 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 1 May 2020 19:40:59 -0400 Subject: [PATCH 07/11] Use iterator chaining over hash insertion --- cli/src/main.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 6f2850b1e0..8fbe4ae77b 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -367,11 +367,9 @@ fn gen( jump_counter: arena.alloc(0), }; - // TODO remove this - decls_by_id.insert(loaded.module_id, home_decls); - // Populate Procs from decls - for (_, mut decls) in decls_by_id.drain() { + // Add modules' decls to Procs + for (_, mut decls) in decls_by_id.drain().chain(std::iter::once((loaded.module_id, home_decls))) { for decl in decls.drain(..) { use roc_can::def::Declaration::*; use roc_can::expr::Expr::*; From f47d96e1a484de1db063d6b25c1b42c429ee2c13 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 1 May 2020 21:56:44 -0400 Subject: [PATCH 08/11] Update a comment --- compiler/builtins/src/std.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 09a51ce1b7..55ed8dc92f 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -35,8 +35,7 @@ pub fn standard_stdlib() -> StdLib { } } -/// Keep this up to date by hand! -/// +/// Keep this up to date by hand! It's the number of builtin aliases that are imported by default. const NUM_BUILTIN_IMPORTS: usize = 7; /// These can be shared between definitions, they will get instantiated when converted to Type From 101dc80ae5aa4bc8edd2ef4590c6665681bd4e8d Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 1 May 2020 21:57:33 -0400 Subject: [PATCH 09/11] Move parse::operator into module::operator --- cli/src/main.rs | 6 ++++-- compiler/can/src/expr.rs | 2 +- compiler/can/src/operator.rs | 8 ++++---- compiler/fmt/src/expr.rs | 3 +-- compiler/gen/tests/helpers/mod.rs | 2 +- compiler/module/src/lib.rs | 1 + compiler/{parse => module}/src/operator.rs | 0 compiler/parse/src/ast.rs | 3 +-- compiler/parse/src/expr.rs | 2 +- compiler/parse/src/lib.rs | 1 - compiler/parse/tests/test_parse.rs | 5 ++--- compiler/problem/src/can.rs | 2 +- compiler/reporting/src/report.rs | 2 +- 13 files changed, 18 insertions(+), 19 deletions(-) rename compiler/{parse => module}/src/operator.rs (100%) diff --git a/cli/src/main.rs b/cli/src/main.rs index 8fbe4ae77b..239f10a31b 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -367,9 +367,11 @@ fn gen( jump_counter: arena.alloc(0), }; - // Add modules' decls to Procs - for (_, mut decls) in decls_by_id.drain().chain(std::iter::once((loaded.module_id, home_decls))) { + for (_, mut decls) in decls_by_id + .drain() + .chain(std::iter::once((loaded.module_id, home_decls))) + { for decl in decls.drain(..) { use roc_can::def::Declaration::*; use roc_can::expr::Expr::*; diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 1289a61730..22d627824d 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -10,9 +10,9 @@ use crate::procedure::References; use crate::scope::Scope; use roc_collections::all::{ImSet, MutMap, MutSet, SendMap}; use roc_module::ident::{Lowercase, TagName}; +use roc_module::operator::CalledVia; use roc_module::symbol::Symbol; use roc_parse::ast; -use roc_parse::operator::CalledVia; use roc_parse::pattern::PatternType::*; use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError}; use roc_region::all::{Located, Region}; diff --git a/compiler/can/src/operator.rs b/compiler/can/src/operator.rs index 297b3d4bcf..6e005d9396 100644 --- a/compiler/can/src/operator.rs +++ b/compiler/can/src/operator.rs @@ -1,10 +1,10 @@ use bumpalo::collections::Vec; use bumpalo::Bump; use roc_module::ident::ModuleName; +use roc_module::operator::BinOp::Pizza; +use roc_module::operator::{BinOp, CalledVia}; use roc_parse::ast::Expr::{self, *}; use roc_parse::ast::{AssignedField, Def, Pattern, WhenBranch}; -use roc_parse::operator::BinOp::Pizza; -use roc_parse::operator::{BinOp, CalledVia}; use roc_region::all::{Located, Region}; // BinOp precedence logic adapted from Gluon by Markus Westerlind, MIT licensed @@ -201,7 +201,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a }) } UnaryOp(loc_arg, loc_op) | Nested(UnaryOp(loc_arg, loc_op)) => { - use roc_parse::operator::UnaryOp::*; + use roc_module::operator::UnaryOp::*; let region = loc_op.region; let op = loc_op.value; @@ -327,7 +327,7 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) { } fn desugar_bin_op<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a Located> { - use roc_parse::operator::Associativity::*; + use roc_module::operator::Associativity::*; use std::cmp::Ordering; let mut infixes = Infixes::new(loc_expr); diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index a25a394d7b..74faab0577 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -4,9 +4,8 @@ use crate::spaces::{ add_spaces, fmt_comments_only, fmt_condition_spaces, fmt_spaces, is_comment, newline, INDENT, }; use bumpalo::collections::{String, Vec}; +use roc_module::operator::{self, BinOp}; use roc_parse::ast::{AssignedField, Base, CommentOrNewline, Expr, Pattern, WhenBranch}; -use roc_parse::operator; -use roc_parse::operator::BinOp; use roc_region::all::Located; pub fn fmt_expr<'a>( diff --git a/compiler/gen/tests/helpers/mod.rs b/compiler/gen/tests/helpers/mod.rs index b3bfae3e2c..60f955af4a 100644 --- a/compiler/gen/tests/helpers/mod.rs +++ b/compiler/gen/tests/helpers/mod.rs @@ -282,7 +282,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut }) .collect(); - //load builtin values + // load builtin values let (_introduced_rigids, constraint) = constrain_imported_values(imports, constraint, &var_store); diff --git a/compiler/module/src/lib.rs b/compiler/module/src/lib.rs index 78777141ce..b8a460f173 100644 --- a/compiler/module/src/lib.rs +++ b/compiler/module/src/lib.rs @@ -12,6 +12,7 @@ #![allow(clippy::large_enum_variant)] pub mod ident; +pub mod operator; pub mod symbol; #[macro_use] diff --git a/compiler/parse/src/operator.rs b/compiler/module/src/operator.rs similarity index 100% rename from compiler/parse/src/operator.rs rename to compiler/module/src/operator.rs diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index d24ea05ad7..b7386146ad 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -1,10 +1,9 @@ use crate::header::ModuleName; use crate::ident::Ident; -use crate::operator::CalledVia; -use crate::operator::{BinOp, UnaryOp}; use bumpalo::collections::String; use bumpalo::collections::Vec; use bumpalo::Bump; +use roc_module::operator::{BinOp, CalledVia, UnaryOp}; use roc_region::all::{Loc, Region}; #[derive(Clone, Debug, PartialEq)] diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index b09240a658..87acc8649c 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -7,7 +7,6 @@ use crate::blankspace::{ use crate::ident::{global_tag_or_ident, ident, lowercase_ident, Ident}; use crate::keyword; use crate::number_literal::number_literal; -use crate::operator::{BinOp, CalledVia, UnaryOp}; use crate::parser::{ self, allocated, char, fail, not, not_followed_by, optional, sep_by1, string, then, unexpected, unexpected_eof, Either, Fail, FailReason, ParseResult, Parser, State, @@ -16,6 +15,7 @@ use crate::type_annotation; use bumpalo::collections::string::String; use bumpalo::collections::Vec; use bumpalo::Bump; +use roc_module::operator::{BinOp, CalledVia, UnaryOp}; use roc_region::all::{Located, Region}; pub fn expr<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { diff --git a/compiler/parse/src/lib.rs b/compiler/parse/src/lib.rs index 6920568252..af7f71fbcf 100644 --- a/compiler/parse/src/lib.rs +++ b/compiler/parse/src/lib.rs @@ -21,7 +21,6 @@ pub mod ident; pub mod keyword; pub mod module; pub mod number_literal; -pub mod operator; pub mod pattern; pub mod problems; pub mod string_literal; diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index c66e250cc0..7c71a92415 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -18,6 +18,8 @@ mod test_parse { use crate::helpers::parse_with; use bumpalo::collections::vec::Vec; use bumpalo::{self, Bump}; + use roc_module::operator::BinOp::*; + use roc_module::operator::{CalledVia, UnaryOp}; use roc_parse::ast::AssignedField::*; use roc_parse::ast::CommentOrNewline::*; use roc_parse::ast::Expr::{self, *}; @@ -27,9 +29,6 @@ mod test_parse { }; use roc_parse::header::ModuleName; use roc_parse::module::{interface_header, module_defs}; - use roc_parse::operator::BinOp::*; - use roc_parse::operator::CalledVia; - use roc_parse::operator::UnaryOp; use roc_parse::parser::{Fail, FailReason, Parser, State}; use roc_region::all::{Located, Region}; use std::{f64, i64}; diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index 0035b7a5b8..5f83be28b0 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -1,8 +1,8 @@ use inlinable_string::InlinableString; use roc_collections::all::MutSet; use roc_module::ident::{Ident, Lowercase, TagName}; +use roc_module::operator::BinOp; use roc_module::symbol::{ModuleId, Symbol}; -use roc_parse::operator::BinOp; use roc_parse::pattern::PatternType; use roc_region::all::{Located, Region}; diff --git a/compiler/reporting/src/report.rs b/compiler/reporting/src/report.rs index 25cb34c2a9..124bec113a 100644 --- a/compiler/reporting/src/report.rs +++ b/compiler/reporting/src/report.rs @@ -299,7 +299,7 @@ impl<'a> RocDocAllocator<'a> { pub fn binop( &'a self, - content: roc_parse::operator::BinOp, + content: roc_module::operator::BinOp, ) -> DocBuilder<'a, Self, Annotation> { self.text(content.to_string()).annotate(Annotation::BinOp) } From 2679077de91e32842c04967bbad4ae101688e2e3 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 1 May 2020 21:59:38 -0400 Subject: [PATCH 10/11] Attempt to add builtin_defs to CLI --- cli/src/main.rs | 90 ++++++----- compiler/builtins/src/std.rs | 297 ++++++++++++++++++++++++++++++++++ compiler/can/src/builtins.rs | 303 ----------------------------------- compiler/can/src/lib.rs | 1 - 4 files changed, 347 insertions(+), 344 deletions(-) delete mode 100644 compiler/can/src/builtins.rs diff --git a/cli/src/main.rs b/cli/src/main.rs index 239f10a31b..f46111235e 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -3,11 +3,16 @@ extern crate roc_reporting; #[macro_use] extern crate clap; use bumpalo::Bump; +use clap::{App, Arg, ArgMatches}; use inkwell::context::Context; use inkwell::module::Linkage; use inkwell::passes::PassManager; +use inkwell::targets::{ + CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple, +}; use inkwell::types::BasicType; use inkwell::OptimizationLevel; +use roc_can::def::Def; use roc_collections::all::ImMap; use roc_collections::all::MutMap; use roc_gen::llvm::build::{ @@ -18,15 +23,10 @@ use roc_load::file::{LoadedModule, LoadingProblem}; use roc_module::symbol::Symbol; use roc_mono::expr::{Env, Expr, PartialProc, Procs}; use roc_mono::layout::Layout; -use std::time::SystemTime; - -use clap::{App, Arg, ArgMatches}; -use inkwell::targets::{ - CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple, -}; use std::io::{self, ErrorKind}; use std::path::{Path, PathBuf}; use std::process; +use std::time::SystemTime; use target_lexicon::{Architecture, OperatingSystem, Triple, Vendor}; use tokio::process::Command; use tokio::runtime::Builder; @@ -367,6 +367,49 @@ fn gen( jump_counter: arena.alloc(0), }; + let add_def_to_procs = |def: Def| { + use roc_can::expr::Expr::*; + use roc_can::pattern::Pattern::*; + + match def.loc_pattern.value { + Identifier(symbol) => { + match def.loc_expr.value { + Closure(annotation, _, _, loc_args, boxed_body) => { + let (loc_body, ret_var) = *boxed_body; + + procs.insert_closure( + &mut mono_env, + Some(symbol), + annotation, + loc_args, + loc_body, + ret_var, + ); + } + body => { + let proc = PartialProc { + annotation: def.expr_var, + // This is a 0-arity thunk, so it has no arguments. + patterns: bumpalo::collections::Vec::new_in(arena), + body, + }; + + procs.user_defined.insert(symbol, proc); + procs.module_thunks.insert(symbol); + } + }; + } + other => { + todo!("TODO gracefully handle Declare({:?})", other); + } + } + }; + + // Initialize Procs from builtins + for def in roc_can::builtins::builtin_defs() { + add_def_to_procs(def); + } + // Add modules' decls to Procs for (_, mut decls) in decls_by_id .drain() @@ -374,42 +417,9 @@ fn gen( { for decl in decls.drain(..) { use roc_can::def::Declaration::*; - use roc_can::expr::Expr::*; - use roc_can::pattern::Pattern::*; match decl { - Declare(def) => match def.loc_pattern.value { - Identifier(symbol) => { - match def.loc_expr.value { - Closure(annotation, _, _, loc_args, boxed_body) => { - let (loc_body, ret_var) = *boxed_body; - - procs.insert_closure( - &mut mono_env, - Some(symbol), - annotation, - loc_args, - loc_body, - ret_var, - ); - } - body => { - let proc = PartialProc { - annotation: def.expr_var, - // This is a 0-arity thunk, so it has no arguments. - patterns: bumpalo::collections::Vec::new_in(arena), - body, - }; - - procs.user_defined.insert(symbol, proc); - procs.module_thunks.insert(symbol); - } - }; - } - other => { - todo!("TODO gracefully handle Declare({:?})", other); - } - }, + Declare(def) => add_def_to_procs(def), DeclareRec(_defs) => { todo!("TODO support DeclareRec"); } diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 55ed8dc92f..5dd3044a2b 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -1,9 +1,15 @@ +use roc_can::def::Def; +use roc_can::expr::Expr; +use roc_can::expr::Recursive; +use roc_collections::all::SendMap; use roc_collections::all::{default_hasher, MutMap, MutSet}; use roc_module::ident::TagName; +use roc_module::operator::CalledVia; use roc_module::symbol::Symbol; use roc_region::all::{Located, Region}; use roc_types::solved_types::{BuiltinAlias, SolvedType}; use roc_types::subs::VarId; +use roc_types::subs::Variable; use std::collections::HashMap; #[derive(Clone, Copy, Debug)] @@ -39,10 +45,32 @@ pub fn standard_stdlib() -> StdLib { const NUM_BUILTIN_IMPORTS: usize = 7; /// These can be shared between definitions, they will get instantiated when converted to Type +const TVAR_NONE: VarId = VarId::from_u32(0); const TVAR1: VarId = VarId::from_u32(1); const TVAR2: VarId = VarId::from_u32(2); const TVAR3: VarId = VarId::from_u32(3); +/// Some builtins cannot be constructed in code gen alone, and need to be defined +/// as separate Roc defs. For example, List.get has this type: +/// +/// List.get : List elem, Int -> Result elem [ OutOfBounds ]* +/// +/// Because this returns an open tag union for its Err type, it's not possible +/// for code gen to return a hardcoded value for OutOfBounds. For example, +/// if this Result unifies to [ Foo, OutOfBounds ] then OutOfBOunds will +/// get assigned the number 1 (because Foo got 0 alphabetically), whereas +/// if it unifies to [ OutOfBounds, Qux ] then OutOfBounds will get the number 0. +/// +/// Getting these numbers right requires having List.get participate in the +/// normal type-checking and monomorphization processes. As such, this function +/// returns a normal def for List.get, which performs a bounds check and then +/// delegates to the compiler-internal List.getUnsafe function to do the actual +/// lookup (if the bounds check passed). That internal function is hardcoded in code gen, +/// which works fine because it doesn't involve any open tag unions. +pub fn builtin_defs() -> Vec { + vec![list_get(), list_first(), int_div()] +} + pub fn aliases() -> MutMap { let mut aliases = HashMap::with_capacity_and_hasher(NUM_BUILTIN_IMPORTS, default_hasher()); @@ -624,3 +652,272 @@ fn set_type(a: SolvedType) -> SolvedType { fn map_type(key: SolvedType, value: SolvedType) -> SolvedType { SolvedType::Apply(Symbol::MAP_MAP, vec![key, value]) } + +#[inline(always)] +fn no_region(value: T) -> Located { + Located { + region: Region::zero(), + value, + } +} + +#[inline(always)] +fn tag(name: &'static str, args: Vec, var_store: &VarStore) -> Expr { + Expr::Tag { + variant_var: var_store.fresh(), + ext_var: var_store.fresh(), + name: TagName::Global(name.into()), + arguments: args + .into_iter() + .map(|expr| (var_store.fresh(), no_region(expr))) + .collect::)>>(), + } +} + +#[inline(always)] +fn call(symbol: Symbol, args: Vec, var_store: &VarStore) -> Expr { + Expr::Call( + Box::new(( + var_store.fresh(), + no_region(Expr::Var(symbol)), + var_store.fresh(), + )), + args.into_iter() + .map(|expr| (var_store.fresh(), no_region(expr))) + .collect::)>>(), + CalledVia::Space, + ) +} + +#[inline(always)] +fn defn(fn_name: Symbol, args: Vec, var_store: &VarStore, body: Expr) -> Def { + use roc_can::expr::Expr::*; + use roc_can::pattern::Pattern::*; + + let closure_args = args + .into_iter() + .map(|symbol| (var_store.fresh(), no_region(Identifier(symbol)))) + .collect(); + + let expr = Closure( + var_store.fresh(), + fn_name, + Recursive::NotRecursive, + closure_args, + Box::new((no_region(body), var_store.fresh())), + ); + + Def { + loc_pattern: no_region(Identifier(fn_name)), + loc_expr: no_region(expr), + expr_var: var_store.fresh(), + pattern_vars: SendMap::default(), + annotation: None, + } +} + +/// List.get : List elem, Int -> Result elem [ OutOfBounds ]* +fn list_get(var_store: &VarStore) -> Def { + use roc_can::expr::Expr::*; + + defn( + Symbol::LIST_GET, + vec![Symbol::LIST_GET_ARG_LIST, Symbol::LIST_GET_ARG_INDEX], + var_store, + // Perform a bounds check. If it passes, delegate to List.#getUnsafe + If { + cond_var: var_store.fresh(), + branch_var: var_store.fresh(), + branches: vec![( + // if-condition + no_region( + // index < List.len list + call( + Symbol::NUM_LT, + vec![ + Var(Symbol::LIST_GET_ARG_INDEX), + call( + Symbol::LIST_LEN, + vec![Var(Symbol::LIST_GET_ARG_LIST)], + var_store, + ), + ], + var_store, + ), + ), + // then-branch + no_region( + // Ok + tag( + "Ok", + vec![ + // List.getUnsafe list index + Call( + Box::new(( + var_store.fresh(), + no_region(Var(Symbol::LIST_GET_UNSAFE)), + var_store.fresh(), + )), + vec![ + (var_store.fresh(), no_region(Var(Symbol::LIST_GET_ARG_LIST))), + ( + var_store.fresh(), + no_region(Var(Symbol::LIST_GET_ARG_INDEX)), + ), + ], + CalledVia::Space, + ), + ], + var_store, + ), + ), + )], + final_else: Box::new( + // else-branch + no_region( + // Err + tag( + "Err", + vec![tag("OutOfBounds", Vec::new(), var_store)], + var_store, + ), + ), + ), + }, + ) +} + +/// Int.div : Int, Int -> Result Int [ DivByZero ]* +fn int_div(var_store: &VarStore) -> Def { + use roc_can::expr::Expr::*; + use roc_can::pattern::Pattern::*; + + let args = vec![ + ( + var_store.fresh(), + no_region(Identifier(Symbol::INT_DIV_ARG_NUMERATOR)), + ), + ( + var_store.fresh(), + no_region(Identifier(Symbol::INT_DIV_ARG_DENOMINATOR)), + ), + ]; + + let body = If { + branch_var: var_store.fresh(), + cond_var: var_store.fresh(), + branches: vec![( + // if-condition + no_region( + // Int.eq denominator 0 + call( + Symbol::INT_NEQ_I64, + vec![ + Var(Symbol::INT_DIV_ARG_DENOMINATOR), + (Int(var_store.fresh(), 0)), + ], + var_store, + ), + ), + // denominator was not zero + no_region( + // Ok (Int.#divUnsafe numerator denominator) + tag( + "Ok", + vec![ + // Int.#divUnsafe numerator denominator + call( + Symbol::INT_DIV_UNSAFE, + vec![ + (Var(Symbol::INT_DIV_ARG_NUMERATOR)), + (Var(Symbol::INT_DIV_ARG_DENOMINATOR)), + ], + var_store, + ), + ], + var_store, + ), + ), + )], + final_else: Box::new( + // denominator was zero + no_region(tag( + "Err", + vec![tag("DivByZero", Vec::new(), var_store)], + var_store, + )), + ), + }; + + let expr = Closure( + var_store.fresh(), + Symbol::INT_DIV, + Recursive::NotRecursive, + args, + Box::new((no_region(body), var_store.fresh())), + ); + + Def { + loc_pattern: no_region(Identifier(Symbol::INT_DIV)), + loc_expr: no_region(expr), + expr_var: var_store.fresh(), + pattern_vars: SendMap::default(), + annotation: None, + } +} + +/// List.first : List elem -> Result elem [ ListWasEmpty ]* +fn list_first(var_store: &VarStore) -> Def { + use roc_can::expr::Expr::*; + + defn( + Symbol::LIST_FIRST, + vec![Symbol::LIST_FIRST_ARG], + var_store, + // Perform a bounds check. If it passes, delegate to List.getUnsafe. + If { + // TODO Use "when" instead of "if" so that we can have False be the first branch. + // We want that for branch prediction; usually we expect the list to be nonempty. + cond_var: var_store.fresh(), + branch_var: var_store.fresh(), + branches: vec![( + // if-condition + no_region( + // List.isEmpty list + call( + Symbol::LIST_IS_EMPTY, + vec![Var(Symbol::LIST_FIRST_ARG)], + var_store, + ), + ), + // list was empty + no_region( + // Err ListWasEmpty + tag( + "Err", + vec![tag("ListWasEmpty", Vec::new(), var_store)], + var_store, + ), + ), + )], + final_else: Box::new( + // list was not empty + no_region( + // Ok (List.#getUnsafe list 0) + tag( + "Ok", + vec![ + // List.#getUnsafe list 0 + call( + Symbol::LIST_GET_UNSAFE, + vec![(Var(Symbol::LIST_FIRST_ARG)), (Int(var_store.fresh(), 0))], + var_store, + ), + ], + var_store, + ), + ), + ), + }, + ) +} diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs deleted file mode 100644 index 00d7266c9e..0000000000 --- a/compiler/can/src/builtins.rs +++ /dev/null @@ -1,303 +0,0 @@ -use crate::def::Def; -use crate::expr::Expr; -use crate::expr::Recursive; -use roc_collections::all::SendMap; -use roc_module::ident::TagName; -use roc_module::symbol::Symbol; -use roc_parse::operator::CalledVia; -use roc_region::all::{Located, Region}; -use roc_types::subs::{VarStore, Variable}; - -/// Some builtins cannot be constructed in code gen alone, and need to be defined -/// as separate Roc defs. For example, List.get has this type: -/// -/// List.get : List elem, Int -> Result elem [ OutOfBounds ]* -/// -/// Because this returns an open tag union for its Err type, it's not possible -/// for code gen to return a hardcoded value for OutOfBounds. For example, -/// if this Result unifies to [ Foo, OutOfBounds ] then OutOfBOunds will -/// get assigned the number 1 (because Foo got 0 alphabetically), whereas -/// if it unifies to [ OutOfBounds, Qux ] then OutOfBounds will get the number 0. -/// -/// Getting these numbers right requires having List.get participate in the -/// normal type-checking and monomorphization processes. As such, this function -/// returns a normal def for List.get, which performs a bounds check and then -/// delegates to the compiler-internal List.getUnsafe function to do the actual -/// lookup (if the bounds check passed). That internal function is hardcoded in code gen, -/// which works fine because it doesn't involve any open tag unions. -pub fn builtin_defs(var_store: &VarStore) -> Vec { - vec![ - list_get(var_store), - list_first(var_store), - int_div(var_store), - ] -} - -/// List.get : List elem, Int -> Result elem [ OutOfBounds ]* -fn list_get(var_store: &VarStore) -> Def { - use crate::expr::Expr::*; - - defn( - Symbol::LIST_GET, - vec![Symbol::LIST_GET_ARG_LIST, Symbol::LIST_GET_ARG_INDEX], - var_store, - // Perform a bounds check. If it passes, delegate to List.#getUnsafe - If { - cond_var: var_store.fresh(), - branch_var: var_store.fresh(), - branches: vec![( - // if-condition - no_region( - // index < List.len list - call( - Symbol::NUM_LT, - vec![ - Var(Symbol::LIST_GET_ARG_INDEX), - call( - Symbol::LIST_LEN, - vec![Var(Symbol::LIST_GET_ARG_LIST)], - var_store, - ), - ], - var_store, - ), - ), - // then-branch - no_region( - // Ok - tag( - "Ok", - vec![ - // List.getUnsafe list index - Call( - Box::new(( - var_store.fresh(), - no_region(Var(Symbol::LIST_GET_UNSAFE)), - var_store.fresh(), - )), - vec![ - (var_store.fresh(), no_region(Var(Symbol::LIST_GET_ARG_LIST))), - ( - var_store.fresh(), - no_region(Var(Symbol::LIST_GET_ARG_INDEX)), - ), - ], - CalledVia::Space, - ), - ], - var_store, - ), - ), - )], - final_else: Box::new( - // else-branch - no_region( - // Err - tag( - "Err", - vec![tag("OutOfBounds", Vec::new(), var_store)], - var_store, - ), - ), - ), - }, - ) -} - -/// Int.div : Int, Int -> Result Int [ DivByZero ]* -fn int_div(var_store: &VarStore) -> Def { - use crate::expr::Expr::*; - use crate::pattern::Pattern::*; - - let args = vec![ - ( - var_store.fresh(), - no_region(Identifier(Symbol::INT_DIV_ARG_NUMERATOR)), - ), - ( - var_store.fresh(), - no_region(Identifier(Symbol::INT_DIV_ARG_DENOMINATOR)), - ), - ]; - - let body = If { - branch_var: var_store.fresh(), - cond_var: var_store.fresh(), - branches: vec![( - // if-condition - no_region( - // Int.eq denominator 0 - call( - Symbol::INT_NEQ_I64, - vec![ - Var(Symbol::INT_DIV_ARG_DENOMINATOR), - (Int(var_store.fresh(), 0)), - ], - var_store, - ), - ), - // denominator was not zero - no_region( - // Ok (Int.#divUnsafe numerator denominator) - tag( - "Ok", - vec![ - // Int.#divUnsafe numerator denominator - call( - Symbol::INT_DIV_UNSAFE, - vec![ - (Var(Symbol::INT_DIV_ARG_NUMERATOR)), - (Var(Symbol::INT_DIV_ARG_DENOMINATOR)), - ], - var_store, - ), - ], - var_store, - ), - ), - )], - final_else: Box::new( - // denominator was zero - no_region(tag( - "Err", - vec![tag("DivByZero", Vec::new(), var_store)], - var_store, - )), - ), - }; - - let expr = Closure( - var_store.fresh(), - Symbol::INT_DIV, - Recursive::NotRecursive, - args, - Box::new((no_region(body), var_store.fresh())), - ); - - Def { - loc_pattern: no_region(Identifier(Symbol::INT_DIV)), - loc_expr: no_region(expr), - expr_var: var_store.fresh(), - pattern_vars: SendMap::default(), - annotation: None, - } -} - -/// List.first : List elem -> Result elem [ ListWasEmpty ]* -fn list_first(var_store: &VarStore) -> Def { - use crate::expr::Expr::*; - - defn( - Symbol::LIST_FIRST, - vec![Symbol::LIST_FIRST_ARG], - var_store, - // Perform a bounds check. If it passes, delegate to List.getUnsafe. - If { - // TODO Use "when" instead of "if" so that we can have False be the first branch. - // We want that for branch prediction; usually we expect the list to be nonempty. - cond_var: var_store.fresh(), - branch_var: var_store.fresh(), - branches: vec![( - // if-condition - no_region( - // List.isEmpty list - call( - Symbol::LIST_IS_EMPTY, - vec![Var(Symbol::LIST_FIRST_ARG)], - var_store, - ), - ), - // list was empty - no_region( - // Err ListWasEmpty - tag( - "Err", - vec![tag("ListWasEmpty", Vec::new(), var_store)], - var_store, - ), - ), - )], - final_else: Box::new( - // list was not empty - no_region( - // Ok (List.#getUnsafe list 0) - tag( - "Ok", - vec![ - // List.#getUnsafe list 0 - call( - Symbol::LIST_GET_UNSAFE, - vec![(Var(Symbol::LIST_FIRST_ARG)), (Int(var_store.fresh(), 0))], - var_store, - ), - ], - var_store, - ), - ), - ), - }, - ) -} - -#[inline(always)] -fn no_region(value: T) -> Located { - Located { - region: Region::zero(), - value, - } -} - -#[inline(always)] -fn tag(name: &'static str, args: Vec, var_store: &VarStore) -> Expr { - Expr::Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: TagName::Global(name.into()), - arguments: args - .into_iter() - .map(|expr| (var_store.fresh(), no_region(expr))) - .collect::)>>(), - } -} - -#[inline(always)] -fn call(symbol: Symbol, args: Vec, var_store: &VarStore) -> Expr { - Expr::Call( - Box::new(( - var_store.fresh(), - no_region(Expr::Var(symbol)), - var_store.fresh(), - )), - args.into_iter() - .map(|expr| (var_store.fresh(), no_region(expr))) - .collect::)>>(), - CalledVia::Space, - ) -} - -#[inline(always)] -fn defn(fn_name: Symbol, args: Vec, var_store: &VarStore, body: Expr) -> Def { - use crate::expr::Expr::*; - use crate::pattern::Pattern::*; - - let closure_args = args - .into_iter() - .map(|symbol| (var_store.fresh(), no_region(Identifier(symbol)))) - .collect(); - - let expr = Closure( - var_store.fresh(), - fn_name, - Recursive::NotRecursive, - closure_args, - Box::new((no_region(body), var_store.fresh())), - ); - - Def { - loc_pattern: no_region(Identifier(fn_name)), - loc_expr: no_region(expr), - expr_var: var_store.fresh(), - pattern_vars: SendMap::default(), - annotation: None, - } -} diff --git a/compiler/can/src/lib.rs b/compiler/can/src/lib.rs index 44c7bf3faa..ff334ae77a 100644 --- a/compiler/can/src/lib.rs +++ b/compiler/can/src/lib.rs @@ -11,7 +11,6 @@ // re-enable this when working on performance optimizations than have it block PRs. #![allow(clippy::large_enum_variant)] pub mod annotation; -pub mod builtins; pub mod constraint; pub mod def; pub mod env; From 13dd937a7149329811b9be473eb7c7339a2706ef Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 1 May 2020 21:59:40 -0400 Subject: [PATCH 11/11] Revert "Attempt to add builtin_defs to CLI" This reverts commit 2679077de91e32842c04967bbad4ae101688e2e3. --- cli/src/main.rs | 90 +++++------ compiler/builtins/src/std.rs | 297 ---------------------------------- compiler/can/src/builtins.rs | 303 +++++++++++++++++++++++++++++++++++ compiler/can/src/lib.rs | 1 + 4 files changed, 344 insertions(+), 347 deletions(-) create mode 100644 compiler/can/src/builtins.rs diff --git a/cli/src/main.rs b/cli/src/main.rs index f46111235e..239f10a31b 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -3,16 +3,11 @@ extern crate roc_reporting; #[macro_use] extern crate clap; use bumpalo::Bump; -use clap::{App, Arg, ArgMatches}; use inkwell::context::Context; use inkwell::module::Linkage; use inkwell::passes::PassManager; -use inkwell::targets::{ - CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple, -}; use inkwell::types::BasicType; use inkwell::OptimizationLevel; -use roc_can::def::Def; use roc_collections::all::ImMap; use roc_collections::all::MutMap; use roc_gen::llvm::build::{ @@ -23,10 +18,15 @@ use roc_load::file::{LoadedModule, LoadingProblem}; use roc_module::symbol::Symbol; use roc_mono::expr::{Env, Expr, PartialProc, Procs}; use roc_mono::layout::Layout; +use std::time::SystemTime; + +use clap::{App, Arg, ArgMatches}; +use inkwell::targets::{ + CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple, +}; use std::io::{self, ErrorKind}; use std::path::{Path, PathBuf}; use std::process; -use std::time::SystemTime; use target_lexicon::{Architecture, OperatingSystem, Triple, Vendor}; use tokio::process::Command; use tokio::runtime::Builder; @@ -367,49 +367,6 @@ fn gen( jump_counter: arena.alloc(0), }; - let add_def_to_procs = |def: Def| { - use roc_can::expr::Expr::*; - use roc_can::pattern::Pattern::*; - - match def.loc_pattern.value { - Identifier(symbol) => { - match def.loc_expr.value { - Closure(annotation, _, _, loc_args, boxed_body) => { - let (loc_body, ret_var) = *boxed_body; - - procs.insert_closure( - &mut mono_env, - Some(symbol), - annotation, - loc_args, - loc_body, - ret_var, - ); - } - body => { - let proc = PartialProc { - annotation: def.expr_var, - // This is a 0-arity thunk, so it has no arguments. - patterns: bumpalo::collections::Vec::new_in(arena), - body, - }; - - procs.user_defined.insert(symbol, proc); - procs.module_thunks.insert(symbol); - } - }; - } - other => { - todo!("TODO gracefully handle Declare({:?})", other); - } - } - }; - - // Initialize Procs from builtins - for def in roc_can::builtins::builtin_defs() { - add_def_to_procs(def); - } - // Add modules' decls to Procs for (_, mut decls) in decls_by_id .drain() @@ -417,9 +374,42 @@ fn gen( { for decl in decls.drain(..) { use roc_can::def::Declaration::*; + use roc_can::expr::Expr::*; + use roc_can::pattern::Pattern::*; match decl { - Declare(def) => add_def_to_procs(def), + Declare(def) => match def.loc_pattern.value { + Identifier(symbol) => { + match def.loc_expr.value { + Closure(annotation, _, _, loc_args, boxed_body) => { + let (loc_body, ret_var) = *boxed_body; + + procs.insert_closure( + &mut mono_env, + Some(symbol), + annotation, + loc_args, + loc_body, + ret_var, + ); + } + body => { + let proc = PartialProc { + annotation: def.expr_var, + // This is a 0-arity thunk, so it has no arguments. + patterns: bumpalo::collections::Vec::new_in(arena), + body, + }; + + procs.user_defined.insert(symbol, proc); + procs.module_thunks.insert(symbol); + } + }; + } + other => { + todo!("TODO gracefully handle Declare({:?})", other); + } + }, DeclareRec(_defs) => { todo!("TODO support DeclareRec"); } diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 5dd3044a2b..55ed8dc92f 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -1,15 +1,9 @@ -use roc_can::def::Def; -use roc_can::expr::Expr; -use roc_can::expr::Recursive; -use roc_collections::all::SendMap; use roc_collections::all::{default_hasher, MutMap, MutSet}; use roc_module::ident::TagName; -use roc_module::operator::CalledVia; use roc_module::symbol::Symbol; use roc_region::all::{Located, Region}; use roc_types::solved_types::{BuiltinAlias, SolvedType}; use roc_types::subs::VarId; -use roc_types::subs::Variable; use std::collections::HashMap; #[derive(Clone, Copy, Debug)] @@ -45,32 +39,10 @@ pub fn standard_stdlib() -> StdLib { const NUM_BUILTIN_IMPORTS: usize = 7; /// These can be shared between definitions, they will get instantiated when converted to Type -const TVAR_NONE: VarId = VarId::from_u32(0); const TVAR1: VarId = VarId::from_u32(1); const TVAR2: VarId = VarId::from_u32(2); const TVAR3: VarId = VarId::from_u32(3); -/// Some builtins cannot be constructed in code gen alone, and need to be defined -/// as separate Roc defs. For example, List.get has this type: -/// -/// List.get : List elem, Int -> Result elem [ OutOfBounds ]* -/// -/// Because this returns an open tag union for its Err type, it's not possible -/// for code gen to return a hardcoded value for OutOfBounds. For example, -/// if this Result unifies to [ Foo, OutOfBounds ] then OutOfBOunds will -/// get assigned the number 1 (because Foo got 0 alphabetically), whereas -/// if it unifies to [ OutOfBounds, Qux ] then OutOfBounds will get the number 0. -/// -/// Getting these numbers right requires having List.get participate in the -/// normal type-checking and monomorphization processes. As such, this function -/// returns a normal def for List.get, which performs a bounds check and then -/// delegates to the compiler-internal List.getUnsafe function to do the actual -/// lookup (if the bounds check passed). That internal function is hardcoded in code gen, -/// which works fine because it doesn't involve any open tag unions. -pub fn builtin_defs() -> Vec { - vec![list_get(), list_first(), int_div()] -} - pub fn aliases() -> MutMap { let mut aliases = HashMap::with_capacity_and_hasher(NUM_BUILTIN_IMPORTS, default_hasher()); @@ -652,272 +624,3 @@ fn set_type(a: SolvedType) -> SolvedType { fn map_type(key: SolvedType, value: SolvedType) -> SolvedType { SolvedType::Apply(Symbol::MAP_MAP, vec![key, value]) } - -#[inline(always)] -fn no_region(value: T) -> Located { - Located { - region: Region::zero(), - value, - } -} - -#[inline(always)] -fn tag(name: &'static str, args: Vec, var_store: &VarStore) -> Expr { - Expr::Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: TagName::Global(name.into()), - arguments: args - .into_iter() - .map(|expr| (var_store.fresh(), no_region(expr))) - .collect::)>>(), - } -} - -#[inline(always)] -fn call(symbol: Symbol, args: Vec, var_store: &VarStore) -> Expr { - Expr::Call( - Box::new(( - var_store.fresh(), - no_region(Expr::Var(symbol)), - var_store.fresh(), - )), - args.into_iter() - .map(|expr| (var_store.fresh(), no_region(expr))) - .collect::)>>(), - CalledVia::Space, - ) -} - -#[inline(always)] -fn defn(fn_name: Symbol, args: Vec, var_store: &VarStore, body: Expr) -> Def { - use roc_can::expr::Expr::*; - use roc_can::pattern::Pattern::*; - - let closure_args = args - .into_iter() - .map(|symbol| (var_store.fresh(), no_region(Identifier(symbol)))) - .collect(); - - let expr = Closure( - var_store.fresh(), - fn_name, - Recursive::NotRecursive, - closure_args, - Box::new((no_region(body), var_store.fresh())), - ); - - Def { - loc_pattern: no_region(Identifier(fn_name)), - loc_expr: no_region(expr), - expr_var: var_store.fresh(), - pattern_vars: SendMap::default(), - annotation: None, - } -} - -/// List.get : List elem, Int -> Result elem [ OutOfBounds ]* -fn list_get(var_store: &VarStore) -> Def { - use roc_can::expr::Expr::*; - - defn( - Symbol::LIST_GET, - vec![Symbol::LIST_GET_ARG_LIST, Symbol::LIST_GET_ARG_INDEX], - var_store, - // Perform a bounds check. If it passes, delegate to List.#getUnsafe - If { - cond_var: var_store.fresh(), - branch_var: var_store.fresh(), - branches: vec![( - // if-condition - no_region( - // index < List.len list - call( - Symbol::NUM_LT, - vec![ - Var(Symbol::LIST_GET_ARG_INDEX), - call( - Symbol::LIST_LEN, - vec![Var(Symbol::LIST_GET_ARG_LIST)], - var_store, - ), - ], - var_store, - ), - ), - // then-branch - no_region( - // Ok - tag( - "Ok", - vec![ - // List.getUnsafe list index - Call( - Box::new(( - var_store.fresh(), - no_region(Var(Symbol::LIST_GET_UNSAFE)), - var_store.fresh(), - )), - vec![ - (var_store.fresh(), no_region(Var(Symbol::LIST_GET_ARG_LIST))), - ( - var_store.fresh(), - no_region(Var(Symbol::LIST_GET_ARG_INDEX)), - ), - ], - CalledVia::Space, - ), - ], - var_store, - ), - ), - )], - final_else: Box::new( - // else-branch - no_region( - // Err - tag( - "Err", - vec![tag("OutOfBounds", Vec::new(), var_store)], - var_store, - ), - ), - ), - }, - ) -} - -/// Int.div : Int, Int -> Result Int [ DivByZero ]* -fn int_div(var_store: &VarStore) -> Def { - use roc_can::expr::Expr::*; - use roc_can::pattern::Pattern::*; - - let args = vec![ - ( - var_store.fresh(), - no_region(Identifier(Symbol::INT_DIV_ARG_NUMERATOR)), - ), - ( - var_store.fresh(), - no_region(Identifier(Symbol::INT_DIV_ARG_DENOMINATOR)), - ), - ]; - - let body = If { - branch_var: var_store.fresh(), - cond_var: var_store.fresh(), - branches: vec![( - // if-condition - no_region( - // Int.eq denominator 0 - call( - Symbol::INT_NEQ_I64, - vec![ - Var(Symbol::INT_DIV_ARG_DENOMINATOR), - (Int(var_store.fresh(), 0)), - ], - var_store, - ), - ), - // denominator was not zero - no_region( - // Ok (Int.#divUnsafe numerator denominator) - tag( - "Ok", - vec![ - // Int.#divUnsafe numerator denominator - call( - Symbol::INT_DIV_UNSAFE, - vec![ - (Var(Symbol::INT_DIV_ARG_NUMERATOR)), - (Var(Symbol::INT_DIV_ARG_DENOMINATOR)), - ], - var_store, - ), - ], - var_store, - ), - ), - )], - final_else: Box::new( - // denominator was zero - no_region(tag( - "Err", - vec![tag("DivByZero", Vec::new(), var_store)], - var_store, - )), - ), - }; - - let expr = Closure( - var_store.fresh(), - Symbol::INT_DIV, - Recursive::NotRecursive, - args, - Box::new((no_region(body), var_store.fresh())), - ); - - Def { - loc_pattern: no_region(Identifier(Symbol::INT_DIV)), - loc_expr: no_region(expr), - expr_var: var_store.fresh(), - pattern_vars: SendMap::default(), - annotation: None, - } -} - -/// List.first : List elem -> Result elem [ ListWasEmpty ]* -fn list_first(var_store: &VarStore) -> Def { - use roc_can::expr::Expr::*; - - defn( - Symbol::LIST_FIRST, - vec![Symbol::LIST_FIRST_ARG], - var_store, - // Perform a bounds check. If it passes, delegate to List.getUnsafe. - If { - // TODO Use "when" instead of "if" so that we can have False be the first branch. - // We want that for branch prediction; usually we expect the list to be nonempty. - cond_var: var_store.fresh(), - branch_var: var_store.fresh(), - branches: vec![( - // if-condition - no_region( - // List.isEmpty list - call( - Symbol::LIST_IS_EMPTY, - vec![Var(Symbol::LIST_FIRST_ARG)], - var_store, - ), - ), - // list was empty - no_region( - // Err ListWasEmpty - tag( - "Err", - vec![tag("ListWasEmpty", Vec::new(), var_store)], - var_store, - ), - ), - )], - final_else: Box::new( - // list was not empty - no_region( - // Ok (List.#getUnsafe list 0) - tag( - "Ok", - vec![ - // List.#getUnsafe list 0 - call( - Symbol::LIST_GET_UNSAFE, - vec![(Var(Symbol::LIST_FIRST_ARG)), (Int(var_store.fresh(), 0))], - var_store, - ), - ], - var_store, - ), - ), - ), - }, - ) -} diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs new file mode 100644 index 0000000000..85929ff2b8 --- /dev/null +++ b/compiler/can/src/builtins.rs @@ -0,0 +1,303 @@ +use crate::def::Def; +use crate::expr::Expr; +use crate::expr::Recursive; +use roc_collections::all::SendMap; +use roc_module::ident::TagName; +use roc_module::operator::CalledVia; +use roc_module::symbol::Symbol; +use roc_region::all::{Located, Region}; +use roc_types::subs::{VarStore, Variable}; + +/// Some builtins cannot be constructed in code gen alone, and need to be defined +/// as separate Roc defs. For example, List.get has this type: +/// +/// List.get : List elem, Int -> Result elem [ OutOfBounds ]* +/// +/// Because this returns an open tag union for its Err type, it's not possible +/// for code gen to return a hardcoded value for OutOfBounds. For example, +/// if this Result unifies to [ Foo, OutOfBounds ] then OutOfBOunds will +/// get assigned the number 1 (because Foo got 0 alphabetically), whereas +/// if it unifies to [ OutOfBounds, Qux ] then OutOfBounds will get the number 0. +/// +/// Getting these numbers right requires having List.get participate in the +/// normal type-checking and monomorphization processes. As such, this function +/// returns a normal def for List.get, which performs a bounds check and then +/// delegates to the compiler-internal List.getUnsafe function to do the actual +/// lookup (if the bounds check passed). That internal function is hardcoded in code gen, +/// which works fine because it doesn't involve any open tag unions. +pub fn builtin_defs(var_store: &VarStore) -> Vec { + vec![ + list_get(var_store), + list_first(var_store), + int_div(var_store), + ] +} + +/// List.get : List elem, Int -> Result elem [ OutOfBounds ]* +fn list_get(var_store: &VarStore) -> Def { + use crate::expr::Expr::*; + + defn( + Symbol::LIST_GET, + vec![Symbol::LIST_GET_ARG_LIST, Symbol::LIST_GET_ARG_INDEX], + var_store, + // Perform a bounds check. If it passes, delegate to List.#getUnsafe + If { + cond_var: var_store.fresh(), + branch_var: var_store.fresh(), + branches: vec![( + // if-condition + no_region( + // index < List.len list + call( + Symbol::NUM_LT, + vec![ + Var(Symbol::LIST_GET_ARG_INDEX), + call( + Symbol::LIST_LEN, + vec![Var(Symbol::LIST_GET_ARG_LIST)], + var_store, + ), + ], + var_store, + ), + ), + // then-branch + no_region( + // Ok + tag( + "Ok", + vec![ + // List.getUnsafe list index + Call( + Box::new(( + var_store.fresh(), + no_region(Var(Symbol::LIST_GET_UNSAFE)), + var_store.fresh(), + )), + vec![ + (var_store.fresh(), no_region(Var(Symbol::LIST_GET_ARG_LIST))), + ( + var_store.fresh(), + no_region(Var(Symbol::LIST_GET_ARG_INDEX)), + ), + ], + CalledVia::Space, + ), + ], + var_store, + ), + ), + )], + final_else: Box::new( + // else-branch + no_region( + // Err + tag( + "Err", + vec![tag("OutOfBounds", Vec::new(), var_store)], + var_store, + ), + ), + ), + }, + ) +} + +/// Int.div : Int, Int -> Result Int [ DivByZero ]* +fn int_div(var_store: &VarStore) -> Def { + use crate::expr::Expr::*; + use crate::pattern::Pattern::*; + + let args = vec![ + ( + var_store.fresh(), + no_region(Identifier(Symbol::INT_DIV_ARG_NUMERATOR)), + ), + ( + var_store.fresh(), + no_region(Identifier(Symbol::INT_DIV_ARG_DENOMINATOR)), + ), + ]; + + let body = If { + branch_var: var_store.fresh(), + cond_var: var_store.fresh(), + branches: vec![( + // if-condition + no_region( + // Int.eq denominator 0 + call( + Symbol::INT_NEQ_I64, + vec![ + Var(Symbol::INT_DIV_ARG_DENOMINATOR), + (Int(var_store.fresh(), 0)), + ], + var_store, + ), + ), + // denominator was not zero + no_region( + // Ok (Int.#divUnsafe numerator denominator) + tag( + "Ok", + vec![ + // Int.#divUnsafe numerator denominator + call( + Symbol::INT_DIV_UNSAFE, + vec![ + (Var(Symbol::INT_DIV_ARG_NUMERATOR)), + (Var(Symbol::INT_DIV_ARG_DENOMINATOR)), + ], + var_store, + ), + ], + var_store, + ), + ), + )], + final_else: Box::new( + // denominator was zero + no_region(tag( + "Err", + vec![tag("DivByZero", Vec::new(), var_store)], + var_store, + )), + ), + }; + + let expr = Closure( + var_store.fresh(), + Symbol::INT_DIV, + Recursive::NotRecursive, + args, + Box::new((no_region(body), var_store.fresh())), + ); + + Def { + loc_pattern: no_region(Identifier(Symbol::INT_DIV)), + loc_expr: no_region(expr), + expr_var: var_store.fresh(), + pattern_vars: SendMap::default(), + annotation: None, + } +} + +/// List.first : List elem -> Result elem [ ListWasEmpty ]* +fn list_first(var_store: &VarStore) -> Def { + use crate::expr::Expr::*; + + defn( + Symbol::LIST_FIRST, + vec![Symbol::LIST_FIRST_ARG], + var_store, + // Perform a bounds check. If it passes, delegate to List.getUnsafe. + If { + // TODO Use "when" instead of "if" so that we can have False be the first branch. + // We want that for branch prediction; usually we expect the list to be nonempty. + cond_var: var_store.fresh(), + branch_var: var_store.fresh(), + branches: vec![( + // if-condition + no_region( + // List.isEmpty list + call( + Symbol::LIST_IS_EMPTY, + vec![Var(Symbol::LIST_FIRST_ARG)], + var_store, + ), + ), + // list was empty + no_region( + // Err ListWasEmpty + tag( + "Err", + vec![tag("ListWasEmpty", Vec::new(), var_store)], + var_store, + ), + ), + )], + final_else: Box::new( + // list was not empty + no_region( + // Ok (List.#getUnsafe list 0) + tag( + "Ok", + vec![ + // List.#getUnsafe list 0 + call( + Symbol::LIST_GET_UNSAFE, + vec![(Var(Symbol::LIST_FIRST_ARG)), (Int(var_store.fresh(), 0))], + var_store, + ), + ], + var_store, + ), + ), + ), + }, + ) +} + +#[inline(always)] +fn no_region(value: T) -> Located { + Located { + region: Region::zero(), + value, + } +} + +#[inline(always)] +fn tag(name: &'static str, args: Vec, var_store: &VarStore) -> Expr { + Expr::Tag { + variant_var: var_store.fresh(), + ext_var: var_store.fresh(), + name: TagName::Global(name.into()), + arguments: args + .into_iter() + .map(|expr| (var_store.fresh(), no_region(expr))) + .collect::)>>(), + } +} + +#[inline(always)] +fn call(symbol: Symbol, args: Vec, var_store: &VarStore) -> Expr { + Expr::Call( + Box::new(( + var_store.fresh(), + no_region(Expr::Var(symbol)), + var_store.fresh(), + )), + args.into_iter() + .map(|expr| (var_store.fresh(), no_region(expr))) + .collect::)>>(), + CalledVia::Space, + ) +} + +#[inline(always)] +fn defn(fn_name: Symbol, args: Vec, var_store: &VarStore, body: Expr) -> Def { + use crate::expr::Expr::*; + use crate::pattern::Pattern::*; + + let closure_args = args + .into_iter() + .map(|symbol| (var_store.fresh(), no_region(Identifier(symbol)))) + .collect(); + + let expr = Closure( + var_store.fresh(), + fn_name, + Recursive::NotRecursive, + closure_args, + Box::new((no_region(body), var_store.fresh())), + ); + + Def { + loc_pattern: no_region(Identifier(fn_name)), + loc_expr: no_region(expr), + expr_var: var_store.fresh(), + pattern_vars: SendMap::default(), + annotation: None, + } +} diff --git a/compiler/can/src/lib.rs b/compiler/can/src/lib.rs index ff334ae77a..44c7bf3faa 100644 --- a/compiler/can/src/lib.rs +++ b/compiler/can/src/lib.rs @@ -11,6 +11,7 @@ // re-enable this when working on performance optimizations than have it block PRs. #![allow(clippy::large_enum_variant)] pub mod annotation; +pub mod builtins; pub mod constraint; pub mod def; pub mod env;