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;