Merge branch 'trunk' into int-rem

This commit is contained in:
Richard Feldman 2020-05-13 22:53:31 -04:00 committed by GitHub
commit 7f05678bf8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 1508 additions and 842 deletions

View file

@ -1,5 +1,6 @@
#[macro_use] #[macro_use]
extern crate clap; extern crate clap;
use bumpalo::Bump; use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::module::Linkage; use inkwell::module::Linkage;
@ -8,6 +9,7 @@ use inkwell::types::BasicType;
use inkwell::OptimizationLevel; use inkwell::OptimizationLevel;
use roc_collections::all::ImMap; use roc_collections::all::ImMap;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_gen::layout_id::LayoutIds;
use roc_gen::llvm::build::{ use roc_gen::llvm::build::{
build_proc, build_proc_header, get_call_conventions, module_from_builtins, OptLevel, build_proc, build_proc_header, get_call_conventions, module_from_builtins, OptLevel,
}; };
@ -358,7 +360,7 @@ fn gen(
// Compute main_fn_type before moving subs to Env // Compute main_fn_type before moving subs to Env
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let layout = Layout::from_content(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| { let layout = Layout::new(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| {
panic!( panic!(
"Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", "Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}",
err, subs err, subs
@ -378,9 +380,10 @@ fn gen(
module: arena.alloc(module), module: arena.alloc(module),
ptr_bytes, ptr_bytes,
}; };
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
let mut layout_ids = LayoutIds::default();
let mut procs = Procs::default(); let mut procs = Procs::default();
let mut mono_problems = std::vec::Vec::new(); 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 { let mut mono_env = Env {
arena, arena,
subs: &mut subs, subs: &mut subs,
@ -451,13 +454,17 @@ fn gen(
env.interns.all_ident_ids.insert(home, ident_ids); env.interns.all_ident_ids.insert(home, ident_ids);
let mut headers = Vec::with_capacity(procs.len()); let mut headers = Vec::with_capacity(procs.len());
let (mut proc_map, runtime_errors) = procs.into_map();
assert_eq!(runtime_errors, roc_collections::all::MutSet::default());
// Add all the Proc headers to the module. // Add all the Proc headers to the module.
// We have to do this in a separate pass first, // We have to do this in a separate pass first,
// because their bodies may reference each other. // because their bodies may reference each other.
for (symbol, opt_proc) in procs.as_map().into_iter() { for (symbol, mut procs_by_layout) in proc_map.drain() {
if let Some(proc) = opt_proc { for (layout, proc) in procs_by_layout.drain() {
let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc); let (fn_val, arg_basic_types) =
build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
headers.push((proc, fn_val, arg_basic_types)); headers.push((proc, fn_val, arg_basic_types));
} }
@ -469,7 +476,7 @@ fn gen(
// (This approach means we don't have to defensively clone name here.) // (This approach means we don't have to defensively clone name here.)
// //
// println!("\n\nBuilding and then verifying function {}\n\n", name); // println!("\n\nBuilding and then verifying function {}\n\n", name);
build_proc(&env, proc, &procs, fn_val, arg_basic_types); build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types);
if fn_val.verify(true) { if fn_val.verify(true) {
fpm.run_on(&fn_val); fpm.run_on(&fn_val);
@ -495,10 +502,10 @@ fn gen(
let ret = roc_gen::llvm::build::build_expr( let ret = roc_gen::llvm::build::build_expr(
&env, &env,
&mut layout_ids,
&ImMap::default(), &ImMap::default(),
main_fn, main_fn,
&main_body, &main_body,
&Procs::default(),
); );
builder.build_return(Some(&ret)); builder.build_return(Some(&ret));

View file

@ -14,6 +14,7 @@ use roc_can::scope::Scope;
use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet}; use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet};
use roc_constrain::expr::constrain_expr; use roc_constrain::expr::constrain_expr;
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import}; use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
use roc_gen::layout_id::LayoutIds;
use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel}; use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel};
use roc_gen::llvm::convert::basic_type_from_layout; use roc_gen::llvm::convert::basic_type_from_layout;
use roc_module::ident::Ident; use roc_module::ident::Ident;
@ -218,7 +219,7 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St
let expr_type_str = content_to_string(content.clone(), &subs, home, &interns); let expr_type_str = content_to_string(content.clone(), &subs, home, &interns);
// Compute main_fn_type before moving subs to Env // Compute main_fn_type before moving subs to Env
let layout = Layout::from_content(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| { let layout = Layout::new(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| {
panic!( panic!(
"Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", "Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}",
err, subs err, subs
@ -243,6 +244,7 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St
}; };
let mut procs = Procs::default(); let mut procs = Procs::default();
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
let mut layout_ids = LayoutIds::default();
// Populate Procs and get the low-level Expr from the canonical Expr // Populate Procs and get the low-level Expr from the canonical Expr
let mut mono_problems = Vec::new(); let mut mono_problems = Vec::new();
@ -261,13 +263,21 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St
env.interns.all_ident_ids.insert(home, ident_ids); env.interns.all_ident_ids.insert(home, ident_ids);
let mut headers = Vec::with_capacity(procs.len()); let mut headers = Vec::with_capacity(procs.len());
let (mut proc_map, runtime_errors) = procs.into_map();
assert_eq!(
runtime_errors,
roc_collections::all::MutSet::default(),
"TODO code gen runtime exception functions"
);
// Add all the Proc headers to the module. // Add all the Proc headers to the module.
// We have to do this in a separate pass first, // We have to do this in a separate pass first,
// because their bodies may reference each other. // because their bodies may reference each other.
for (symbol, opt_proc) in procs.as_map().into_iter() { for (symbol, mut procs_by_layout) in proc_map.drain() {
if let Some(proc) = opt_proc { for (layout, proc) in procs_by_layout.drain() {
let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc); let (fn_val, arg_basic_types) =
build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
headers.push((proc, fn_val, arg_basic_types)); headers.push((proc, fn_val, arg_basic_types));
} }
@ -279,7 +289,7 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St
// (This approach means we don't have to defensively clone name here.) // (This approach means we don't have to defensively clone name here.)
// //
// println!("\n\nBuilding and then verifying function {}\n\n", name); // println!("\n\nBuilding and then verifying function {}\n\n", name);
build_proc(&env, proc, &procs, fn_val, arg_basic_types); build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types);
if fn_val.verify(true) { if fn_val.verify(true) {
fpm.run_on(&fn_val); fpm.run_on(&fn_val);
@ -305,10 +315,10 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St
let ret = roc_gen::llvm::build::build_expr( let ret = roc_gen::llvm::build::build_expr(
&env, &env,
&mut layout_ids,
&ImMap::default(), &ImMap::default(),
main_fn, main_fn,
&main_body, &main_body,
&Procs::default(),
); );
builder.build_return(Some(&ret)); builder.build_return(Some(&ret));

View file

@ -4,9 +4,9 @@ interface List
## Types ## Types
## A list of values. ## A sequential list of values.
## ##
## >>> [ 1, 2, 3 ] # a list of ints ## >>> [ 1, 2, 3 ] # a list of numbers
## ##
## >>> [ "a", "b", "c" ] # a list of strings ## >>> [ "a", "b", "c" ] # a list of strings
## ##
@ -20,29 +20,43 @@ interface List
## mixedList = [ IntElem 1, IntElem 2, StrElem "a", StrElem "b" ] ## mixedList = [ IntElem 1, IntElem 2, StrElem "a", StrElem "b" ]
## ``` ## ```
## ##
## Lists are persistent data structures, meaning they are designed for fast copying. ## The maximum size of a #List is limited by the amount of heap memory available
## to the current process. If there is not enough memory available, attempting to
## create the list could crash. (On Linux, where [overcommit](https://www.etalabs.net/overcommit.html)
## is normally enabled, not having enough memory could result in the list appearing
## to be created just fine, but then crashing later.)
## ##
## > Under the hood, large lists are Reduced Radix Balanced Trees. ## > The theoretical maximum length for a list created in Roc is
## > Small lists are stored as flat arrays. This "small list optimization" ## > #Int.highestUlen divided by 2. Attempting to create a list bigger than that
## > applies to lists that take up 8 machine words in memory or fewer, so ## > in Roc code will always fail, although in practice it is likely to fail
## > for example on a 64-bit system, a list of 8 #Int values will be ## > at much smaller lengths due to insufficient memory being available.
## > stored as a flat array instead of as an RRBT.
## ##
## One #List can store up to 2,147,483,648 elements (just over 2 billion). If you need to store more ## ## Performance notes
## elements than that, you can split them into smaller lists and operate ##
## on those instead of on one large #List. This often runs faster in practice, ## Under the hood, a list is a record containing a `len : Ulen` field as well
## even for strings much smaller than 2 gigabytes. ## as a pointer to a flat list of bytes.
##
## This is not a [persistent data structure](https://en.wikipedia.org/wiki/Persistent_data_structure),
## so copying it is not cheap! The reason #List is designed this way is because:
##
## * Copying small lists is typically slightly faster than copying small persistent data structures. This is because, at small sizes, persistent data structures are usually thin wrappers around flat lists anyway. They don't start conferring copying advantages until crossing a certain minimum size threshold.
## Many list operations are no faster with persistent data structures. For example, even if it were a persistent data structure, #List.map, #List.fold, and #List.keepIf would all need to traverse every element in the list and build up the result from scratch.
## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, #List.map, #List.keepIf, and #List.set can all be optimized to perform in-place mutations.
## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way #List worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using #List under the hood!
List elem : @List elem List elem : @List elem
## Initialize ## Initialize
single : elem -> List elem single : elem -> List elem
## If given any number less than 1, returns #[]. empty : List *
repeat : elem, Int -> List elem
range : Int, Int -> List Int repeat : elem, Ulen -> List elem
range : Int a, Int a -> List (Int a)
## TODO I don't think we should have this after all. *Maybe* have an Ok.toList instead?
##
## When given an #Err, returns #[], and when given #Ok, returns a list containing ## When given an #Err, returns #[], and when given #Ok, returns a list containing
## only that value. ## only that value.
## ##
@ -59,8 +73,7 @@ fromResult : Result elem * -> List elem
reverse : List elem -> List elem reverse : List elem -> List elem
sort : List elem, (elem, elem -> [ Eq, Lt, Gt ]) -> List elem sort : List elem, Sorter elem -> List elem
sortBy : List elem, (elem -> field), (field, field -> [ Eq, Lt, Gt ]) -> List elem
## Convert each element in the list to something new, by calling a conversion ## Convert each element in the list to something new, by calling a conversion
## function on each of them. Then return a new list of the converted values. ## function on each of them. Then return a new list of the converted values.
@ -123,7 +136,7 @@ joinMap : List before, (before -> List after) -> List after
## >>> |> List.joinOks ## >>> |> List.joinOks
joinOks : List (Result elem *) -> List elem joinOks : List (Result elem *) -> List elem
## Iterates over the shortest of the given lists and returns a list of `Tuple` ## Iterates over the shortest of the given lists and returns a list of `Pair`
## tags, each wrapping one of the elements in that list, along with the elements ## tags, each wrapping one of the elements in that list, along with the elements
## in the same position in # the other lists. ## in the same position in # the other lists.
## ##
@ -131,12 +144,12 @@ joinOks : List (Result elem *) -> List elem
## ##
## Accepts up to 8 lists. ## Accepts up to 8 lists.
## ##
## > For a generalized version that returns whatever you like, instead of a `Tup`, ## > For a generalized version that returns whatever you like, instead of a `Pair`,
## > see `zipMap`. ## > see `zipMap`.
zip : zip :
List a, List b, -> List [ Tup a b ]* List a, List b, -> List [ Pair a b ]*
List a, List b, List c, -> List [ Tup a b c ]* List a, List b, List c, -> List [ Pair a b c ]*
List a, List b, List c, List d -> List [ Tup a b c d ]* List a, List b, List c, List d -> List [ Pair a b c d ]*
## Like `zip` but you can specify what to do with each element. ## Like `zip` but you can specify what to do with each element.
## ##
@ -157,13 +170,35 @@ zipMap :
## elements for which the function returned `True`. ## elements for which the function returned `True`.
## ##
## >>> List.keepIf [ 1, 2, 3, 4 ] (\num -> num > 2) ## >>> List.keepIf [ 1, 2, 3, 4 ] (\num -> num > 2)
keepIf : List elem, (elem -> Bool) -> List elem ##
## ## Performance Notes
##
## #List.keepIf always returns a list that takes up exactly the same amount
## of memory as the original, even if its length decreases. This is becase it
## can't know in advance exactly how much space it will need, and if it guesses a
## length that's too low, it would have to re-allocate.
##
## (If you want to do an operation like this which reduces the memory footprint
## of the resulting list, you can do two passes over the lis with #List.fold - one
## to calculate the precise new size, and another to populate the new list.)
##
## If given a unique list, #List.keepIf will mutate it in place to assemble the appropriate list.
## If that happens, this function will not allocate any new memory on the heap.
## If all elements in the list end up being kept, Roc will return the original
## list unaltered.
##
keepIf : List elem, (elem -> [True, False]) -> List elem
## Run the given function on each element of a list, and return all the ## Run the given function on each element of a list, and return all the
## elements for which the function returned `False`. ## elements for which the function returned `False`.
## ##
## >>> List.dropIf [ 1, 2, 3, 4 ] (\num -> num > 2) ## >>> List.dropIf [ 1, 2, 3, 4 ] (\num -> num > 2)
dropIf : List elem, (elem -> Bool) -> List elem ##
## ## Performance Notes
##
## #List.dropIf has the same performance characteristics as #List.keepIf.
## See its documentation for details on those characteristics!
dropIf : List elem, (elem -> [True, False]) -> List elem
## Takes the requested number of elements from the front of a list ## Takes the requested number of elements from the front of a list
## and returns them. ## and returns them.
@ -187,6 +222,22 @@ take : List elem, Int -> List elem
## >>> drop 5 [ 1, 2 ] ## >>> drop 5 [ 1, 2 ]
drop : List elem, Int -> List elem drop : List elem, Int -> List elem
## Access
first : List elem -> [Ok elem, ListWasEmpty]*
last : List elem -> [Ok elem, ListWasEmpty]*
get : List elem, Ulen -> [Ok elem, OutOfBounds]*
max : List (Num a) -> [Ok (Num a), ListWasEmpty]*
min : List (Num a) -> [Ok (Num a), ListWasEmpty]*
## Modify
set : List elem, Ulen, elem -> List elem
## Deconstruct ## Deconstruct
split : List elem, Int -> { before: List elem, remainder: List elem } split : List elem, Int -> { before: List elem, remainder: List elem }
@ -202,7 +253,7 @@ walkBackwards : List elem, { start : state, step : (state, elem -> state) } -> s
## One #List can store up to 2,147,483,648 elements (just over 2 billion), which ## One #List can store up to 2,147,483,648 elements (just over 2 billion), which
## is exactly equal to the highest valid #I32 value. This means the #U32 this function ## is exactly equal to the highest valid #I32 value. This means the #U32 this function
## returns can always be safely converted to an #I32 without losing any data. ## returns can always be safely converted to an #I32 without losing any data.
len : List * -> U32 len : List * -> Ulen
isEmpty : List * -> Bool isEmpty : List * -> Bool
@ -211,4 +262,3 @@ contains : List elem, elem -> Bool
all : List elem, (elem -> Bool) -> Bool all : List elem, (elem -> Bool) -> Bool
any : List elem, (elem -> Bool) -> Bool any : List elem, (elem -> Bool) -> Bool

View file

@ -1,12 +1,9 @@
interface Bool interface Bool
exposes [ Bool, not, equal, notEqual ] exposes [ not, isEq, isNe ]
imports [] imports []
## Either #True or #False.
Bool : [ False, True ]
## Returns #False when given #True, and vice versa. ## Returns #False when given #True, and vice versa.
not : Bool -> Bool not : [True, False] -> [True, False]
## Returns #True when given #True and #True, and #False when either argument is #False. ## Returns #True when given #True and #True, and #False when either argument is #False.
## ##
@ -60,7 +57,7 @@ not : Bool -> Bool
## That said, in practice the `&& Str.isEmpty str` approach will typically run ## That said, in practice the `&& Str.isEmpty str` approach will typically run
## faster than the `&& emptyStr` approach - both for `Str.isEmpty` in particular ## faster than the `&& emptyStr` approach - both for `Str.isEmpty` in particular
## as well as for most functions in general. ## as well as for most functions in general.
and : Bool, Bool -> Bool and : [True, False], [True, False] -> [True, False]
## Returns #True when given #True for either argument, and #False only when given #False and #False. ## Returns #True when given #True for either argument, and #False only when given #False and #False.
@ -82,7 +79,10 @@ and : Bool, Bool -> Bool
## #True (causing it to immediately returns #True). ## #True (causing it to immediately returns #True).
## ##
## See the performance notes for #Bool.and for details. ## See the performance notes for #Bool.and for details.
or : Bool, Bool -> Bool or : [True, False], [True, False] -> [True, False]
## Exclusive or
xor : [True, False], [True, False] -> [True, False]
## Returns #True if the two values are *structurally equal*, and #False otherwise. ## Returns #True if the two values are *structurally equal*, and #False otherwise.
## ##
@ -95,12 +95,16 @@ or : Bool, Bool -> Bool
## 5. Collections (#String, #List, #Map, #Set, and #Bytes) are equal if they are the same length, and also all their corresponding elements are equal. ## 5. Collections (#String, #List, #Map, #Set, and #Bytes) are equal if they are the same length, and also all their corresponding elements are equal.
## 6. All functions are considered equal. (So `Bool.not == Bool.not` will return #True, as you might expect, but also `Num.abs == Num.negate` will return #True, as you might not. This design is because function equality has been formally proven to be undecidable in the general case, and returning #True in all cases turns out to be mostly harmless - especially compared to alternative designs like crashing, making #equal inconvenient to use, and so on.) ## 6. All functions are considered equal. (So `Bool.not == Bool.not` will return #True, as you might expect, but also `Num.abs == Num.negate` will return #True, as you might not. This design is because function equality has been formally proven to be undecidable in the general case, and returning #True in all cases turns out to be mostly harmless - especially compared to alternative designs like crashing, making #equal inconvenient to use, and so on.)
## ##
## This function always crashes when given two functions, or an erroneous
## #Float value (see #Float.isErroneous)
##
## This is the same as the #== operator. ## This is the same as the #== operator.
eq : val, val -> Bool isEq : val, val -> [True, False]
## Calls #eq on the given values, then calls #not on the result. ## Calls #eq on the given values, then calls #not on the result.
## ##
## This is the same as the #=/= operator. ## This is the same as the #!= operator.
notEq : val, val -> Bool isNe : val, val -> [True, False]
notEq = \left, right -> isNe = \left, right ->
not (equal left right) not (equal left right)

View file

@ -37,19 +37,69 @@ interface Float
## ##
## >>> 1_000_000.000_000_001 ## >>> 1_000_000.000_000_001
## ##
## Unlike #Int values, #Float values are imprecise. A classic example of this imprecision: ## Roc supports two types of floating-point numbers:
##
## - *Decimal* floating-point numbers
## - *Binary* floating-point numbers
##
## Decimal floats are precise for decimal calculations. For example:
## ##
## >>> 0.1 + 0.2 ## >>> 0.1 + 0.2
## ##
## Floating point values work this way because of some (very reasonable) hardware design decisions made in 1985, which are hardwired into all modern CPUs. The performance penalty for having things work any other way than this is severe, so Roc defaults to using the one with hardware support. ## Operations on binary floats tend to run *much* faster than operations on
## decimal floats, because almost all processors have dedicated instructions
## for binary floats and not for decimal floats.
## However, binary floats are less precise for decimal calculations.
## ##
## It is possible to build fractional systems with different precision characteristics (for example, storing an #Int numerator and #Int denominator), but their basic arithmetic operations will be unavoidably slower than #Float. ## For example, here is the same `0.1 + 0.2` calculation again, this time putting
## `f64` after the numbers to specify that they should be #F64 binary floats
## instead of the default of decimal floats.
## ##
## See #Float.highest and #Float.lowest for the highest and ## >>> 0.1f64 + 0.2f64
## lowest values that can be held in a #Float.
## ##
## Like #Int, it's possible for #Float operations to overflow. ## If decimal precision is unimportant, binary floats give better performance.
## if they exceed the bounds of #Float.highest and #Float.lowest. When this happens: ## If decimal precision is important - for example, when representing money -
## decimal floats tend to be worth the performance cost.
##
## Usually, Roc's compiler can infer a more specific type than #Float for
## a particular float value, based on how it is used with other numbers. For example:
##
## >>> coordinates : { x : F32, y : F32 }
## >>> coordinates = { x: 1, y: 2.5 }
## >>>
## >>> coordinates.x + 1
##
## On the last line, the compiler infers that the `1` in `+ 1` is an #F32
## beacuse it's being added to `coordinates.x`, which was defined to be an #F32
## on the first line.
##
## Sometimes the compiler has no information about which specific type to pick.
## For example:
##
## >>> 0.1 + 0.2 == 0.3
##
## When this happens, the compiler defaults to choosing #D64 decimal floats.
## If you want something else, you can write (for example) `0.1f32 + 0.2 == 0.3`
## to compare them as #F32 values instead.
##
## Both decimal and binary #Float values conform to the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754#Interchange_formats)
## specification for floating point numbers. Conforming to this specification
## means Roc's binary floats have nearly universal hardware support, and its
## decimal floats have [some hardware support](http://speleotrove.com/decimal/)
## among the rare processors which support decimal float instructions at all.
##
## This specification covers these float formats, all of which Roc supports:
##
## - #F16 (16-bit binary float) # TODO show a table like we do with ints, with the min/max ranges
## - #F32 (32-bit binary float)
## - #F64 (64-bit binary float)
## - #F128 (128-bit binary float)
## - #D32 (32-bit decimal float)
## - #D64 (64-bit decimal float)
## - #D128 (128-bit decimal float)
##
## Like #Int, it's possible for #Float operations to overflow. Like with ints,
## you'll typically get a crash when this happens.
## ##
## * In a development build, you'll get an assertion failure. ## * In a development build, you'll get an assertion failure.
## * In an optimized build, you'll get [`Infinity` or `-Infinity`](https://en.wikipedia.org/wiki/IEEE_754-1985#Positive_and_negative_infinity). ## * In an optimized build, you'll get [`Infinity` or `-Infinity`](https://en.wikipedia.org/wiki/IEEE_754-1985#Positive_and_negative_infinity).
@ -71,10 +121,53 @@ interface Float
## These are very error-prone values, so if you see an assertion fail in ## These are very error-prone values, so if you see an assertion fail in
## developent because of one of them, take it seriously - and try to fix ## developent because of one of them, take it seriously - and try to fix
## the code so that it can't come up in a release! ## the code so that it can't come up in a release!
#FloatingPoint := FloatingPoint ##
## ## Loud versus Quiet errors
## Returned in an #Err by #Float.sqrt when given a negative number. ##
#InvalidSqrt := InvalidSqrt ## Besides precision problems, another reason floats are error-prone
## is that they have quiet error handling built in. For example, in
## a 64-bit floating point number, there are certain patterns of those
## 64 bits which do not represent valid floats; instead, they represent
## invalid results of previous operations.
##
## Whenever any arithmetic operation is performed on an invalid float,
## the result is also invalid. This is called *error propagation*, and
## it is notoriously error-prone. In Roc, using equality operations like
## `==` and `!=` on an invalid float causes a crash. (See #Float.verify
## to check the validity of your float.)
##
## Beause invalid floats are so error-prone, Roc discourages using them.
## Instead, by default it treats them the same way as overflow: by
## crashing whenever any #Float function would otherwise return one.
## You can also use functions like #Float.tryAdd to get an `Ok` or an error
## back so you can gracefully recover from invalid values.
##
## Quiet errors can be useful sometimes. For example, you might want to
## do three floating point calculations in a row, and then gracefully handle
## the situation where any one of the three was invalid. In that situation,
## quiet errors can be more efficient than using three `try` functions, because
## it can have one condition at the end instead of three along the way.
##
## Another potential use for quiet errors is for risky performance optimizations.
## When you are absolutely certain there is no chance of overflow or other
## errors, using a *quiet* operation may save an entry in the instruction cache
## by removing a branch that would always have been predicted correctly.
## Always [measure the performance change](https://youtu.be/r-TLSBdHe1A)
## if you do this! The compiler can optimize away those branches behind the scenes,
## so you may find that using the quiet version expliitly
## makes the code riskier to future change, without actually affecting performance.
##
## ## Performance Notes
##
## Currently, loud errors are implemented using an extra conditional. Although
## this conditional will always be correctly branh-predicted unless an error
## occurs, there is a small effect on the instruction cache, which means
## quiet errors are very slightly more efficient.
##
## Long-term, it's possible that the Roc compiler may be able to implement
## loud errors using *signalling errors* in some situations, which could
## eliminate the performance difference between loud and quiet errors in
## the situation where no error occurs.
## Conversions ## Conversions
@ -167,7 +260,7 @@ tryRecip : Float a -> Result (Float a) [ DivByZero ]*
## Return an approximation of the absolute value of the square root of the #Float. ## Return an approximation of the absolute value of the square root of the #Float.
## ##
## Return #InvalidSqrt if given a negative number. The square root of a negative number is an irrational number, and #Float only supports rational numbers. ## Return #InvalidSqrt if given a negative number or an invalid #Float. The square root of a negative number is an irrational number, and #Float only supports rational numbers.
## ##
## >>> Float.sqrt 4.0 ## >>> Float.sqrt 4.0
## ##
@ -176,7 +269,22 @@ tryRecip : Float a -> Result (Float a) [ DivByZero ]*
## >>> Float.sqrt 0.0 ## >>> Float.sqrt 0.0
## ##
## >>> Float.sqrt -4.0 ## >>> Float.sqrt -4.0
sqrt : Float a -> Result (Float a) [ InvalidSqrt ] sqrt : Float a -> [Ok (Float a), InvalidSqrt]*
## Like #Float.sqrt, but returning a *quiet NaN* if given a negative number.
##
## Quiet NaNs are notoriously more error-prone than explicit #Ok unions,
## so if you're using this instead of #Float.sqrt, be very careful not to let
## potential error cases go unhandled.
##
## ## Performance Notes
##
## This runs faster than #Float.sqrt, but is more error-prone because it makes
## it easier to forget to handle potential error cases. You may not forget
## when you just got done reading this paragraph, but the next person who
## comes along to modify the code may not have read it at all, and might not
## realize the need for seurity checks beause the requirement is implicit.
sqrtQuiet : Float a -> Float a
## Constants ## Constants
@ -198,17 +306,52 @@ asc : Float a, Float a -> [ Eq, Lt, Gt ]
## ##
desc : Float a, Float a -> [ Eq, Lt, Gt ] desc : Float a, Float a -> [ Eq, Lt, Gt ]
## Any float that resulted from a quiet operation may be invalid.
## This verifies whether they are still valid or have become invalid.
##
## >>> Float.verify (Float.quietSqrt -2)
##
## >>> Float.verify (Float.quietSqrt 2)
##
## >>> Float.verify (Float.quietDiv 1 0)
##
## >>> Float.verify (Float.quietDiv -1 0)
##
## Note that even if you personally never use *quiet* operations, any float
## you get from outside your code base, such as the host or a third-party module,
## may be invalid.
verify : Float * -> [ Valid, Infinity, MinusInfinity, NaN ]
## Any float that resulted from a quiet operation may be invalid.
## This returns `True` if the float is still valid.
##
## >>> Float.isValid (Float.quietSqrt -2)
##
## >>> Float.isValid (Float.quietSqrt 2)
##
## >>> Float.isValid (Float.quietDiv 1 0)
##
## >>> Float.isValid (Float.quietDiv -1 0)
##
## This will return `True` if calling #Float.verify on this float returns `Valid`,
## and will return `False` otherwise.
##
## Note that even if you personally never use *quiet* operations, any float
## you get from outside your code base, such as the host or a third-party module,
## may be invalid.
isValid : Float * -> Bool
## Limits ## Limits
## The highest supported #Float value you can have, which is approximately 1.8 × 10^308. ## The highest supported #Float value you can have, which is approximately 1.8 × 10^308.
## ##
## If you go higher than this, your running Roc code will crash - so be careful not to! ## If you go higher than this, your running Roc code will crash - so be careful not to!
highest : Float * maxF64 : Float *
## The lowest supported #Float value you can have, which is approximately -1.8 × 10^308. ## The lowest supported #Float value you can have, which is approximately -1.8 × 10^308.
## ##
## If you go lower than this, your running Roc code will crash - so be careful not to! ## If you go lower than this, your running Roc code will crash - so be careful not to!
lowest : Float * minF64 : Float *
## The highest integer that can be represented as a #Float without # losing precision. ## The highest integer that can be represented as a #Float without # losing precision.
## It is equal to 2^53, which is approximately 9 × 10^15. ## It is equal to 2^53, which is approximately 9 × 10^15.
@ -220,7 +363,7 @@ lowest : Float *
## >>> Float.highestInt + 100 # Increasing may lose precision ## >>> Float.highestInt + 100 # Increasing may lose precision
## ##
## >>> Float.highestInt - 100 # Decreasing is fine - but watch out for lowestLosslessInt! ## >>> Float.highestInt - 100 # Decreasing is fine - but watch out for lowestLosslessInt!
highestInt : Float * maxPreciseInt : Float *
## The lowest integer that can be represented as a #Float without losing precision. ## The lowest integer that can be represented as a #Float without losing precision.
## It is equal to -2^53, which is approximately -9 × 10^15. ## It is equal to -2^53, which is approximately -9 × 10^15.
@ -232,4 +375,4 @@ highestInt : Float *
## >>> Float.lowestIntVal - 100 # Decreasing may lose precision ## >>> Float.lowestIntVal - 100 # Decreasing may lose precision
## ##
## >>> Float.lowestIntVal + 100 # Increasing is fine - but watch out for highestInt! ## >>> Float.lowestIntVal + 100 # Increasing is fine - but watch out for highestInt!
lowestInt : Float * maxPreciseInt : Float *

View file

@ -16,7 +16,7 @@ interface Int
## #U8 is an an example of an integer. It is an unsigned #Int that takes up 8 bits ## #U8 is an an example of an integer. It is an unsigned #Int that takes up 8 bits
## (aka 1 byte) in memory. The `U` is for Unsigned and the 8 is for 8 bits. ## (aka 1 byte) in memory. The `U` is for Unsigned and the 8 is for 8 bits.
## Because it has 8 bits to work with, it can store 256 numbers (2^8), ## Because it has 8 bits to work with, it can store 256 numbers (2^8),
## and because it is unsigned, its lowest value is 0. This means the 256 numbers ## and because it is unsigned, its min value is 0. This means the 256 numbers
## it can store range from 0 to 255. ## it can store range from 0 to 255.
## ##
## #I8 is a signed integer that takes up 8 bits. The `I` is for Integer, since ## #I8 is a signed integer that takes up 8 bits. The `I` is for Integer, since
@ -52,7 +52,7 @@ interface Int
## ##
## * Start by deciding if this integer should allow negative numbers, and choose signed or unsigned accordingly. ## * Start by deciding if this integer should allow negative numbers, and choose signed or unsigned accordingly.
## * Next, think about the range of numbers you expect this number to hold. Choose the smallest size you will never expect to overflow, no matter the inputs your program receives. (Validating inputs for size, and presenting the user with an error if they are too big, can help guard against overflow.) ## * Next, think about the range of numbers you expect this number to hold. Choose the smallest size you will never expect to overflow, no matter the inputs your program receives. (Validating inputs for size, and presenting the user with an error if they are too big, can help guard against overflow.)
## * Finally, if a particular operation is too slow at runtime, and you know the native machine word size on which it will be running (most often either 64-bit or 32-bit), try switching to an integer of that size and see if it makes a meaningful difference. (The difference is typically extremely small.) ## * Finally, if a particular numeric calculation is running too slowly, you can try experimenting with other number sizes. This rarely makes a meaningful difference, but some processors can operate on different number sizes at different speeds.
Int size : Num (@Int size) Int size : Num (@Int size)
## A signed 8-bit integer, ranging from -128 to 127 ## A signed 8-bit integer, ranging from -128 to 127
@ -66,8 +66,8 @@ I64 : Int @I64
U64 : Int @U64 U64 : Int @U64
I128 : Int @I128 I128 : Int @I128
U128 : Int @U128 U128 : Int @U128
ILen : Int @ILen Ilen : Int @Ilen
ULen : Int @ULen Ulen : Int @Ulen
## A 64-bit signed integer. All number literals without decimal points are compatible with #Int values. ## A 64-bit signed integer. All number literals without decimal points are compatible with #Int values.
## ##
@ -91,7 +91,7 @@ ULen : Int @ULen
## ##
## * Larger integer sizes can represent a wider range of numbers. If you absolutely need to represent numbers in a certain range, make sure to pick an integer size that can hold them! ## * Larger integer sizes can represent a wider range of numbers. If you absolutely need to represent numbers in a certain range, make sure to pick an integer size that can hold them!
## * Smaller integer sizes take up less memory. This savings rarely matters in variables and function arguments, but the sizes of integers that you use in data structures can add up. This can also affect whether those data structures fit in [cache lines](https://en.wikipedia.org/wiki/CPU_cache#Cache_performance), which can be a performance bottleneck. ## * Smaller integer sizes take up less memory. This savings rarely matters in variables and function arguments, but the sizes of integers that you use in data structures can add up. This can also affect whether those data structures fit in [cache lines](https://en.wikipedia.org/wiki/CPU_cache#Cache_performance), which can be a performance bottleneck.
## * CPUs typically work fastest on their native [word size](https://en.wikipedia.org/wiki/Word_(computer_architecture)). For example, 64-bit CPUs tend to work fastest on 64-bit integers. Especially if your performance profiling shows that you are CPU bound rather than memory bound, consider #ILen or #ULen. ## * Certain CPUs work faster on some numeric sizes than others. If the CPU is taking too long to run numeric calculations, you may find a performance improvement by experimenting with numeric sizes that are larger than otherwise necessary. However, in practice, doing this typically degrades overall performance, so be careful to measure properly!
## ##
## Here are the different fixed size integer types: ## Here are the different fixed size integer types:
## ##
@ -127,24 +127,19 @@ ULen : Int @ULen
## | ` (over 340 undecillion) 0` | #U128 | 16 Bytes | ## | ` (over 340 undecillion) 0` | #U128 | 16 Bytes |
## | ` 340_282_366_920_938_463_463_374_607_431_768_211_455` | | | ## | ` 340_282_366_920_938_463_463_374_607_431_768_211_455` | | |
## ##
## There are also two variable-size integer types: #Iword and #Uword. ## There are also two variable-size integer types: #Ulen and #Ilen. Their sizes
## Their sizes are determined by the machine word size for the system you're ## are determined by the [machine word length](https://en.wikipedia.org/wiki/Word_(computer_architecture))
## compiling for. For example, on a 64-bit system, #Iword is the same as #I64, ## of the system you're compiling for. (The "len" in their names is short for "length of a machine word.")
## and #Uword is the same as #U64. ## For example, when compiling for a 64-bit target, #Ulen is the same as #U64,
## and #Ilen is the same as #I64. When compiling for a 32-bit target, #Ulen is the same as #U32,
## and #Ilen is the same as #I32. In practice, #Ulen sees much more use than #Ilen.
## ##
## If any operation would result in an #Int that is either too big ## If any operation would result in an #Int that is either too big
## or too small to fit in that range (e.g. calling `Int.highest32 + 1`), ## or too small to fit in that range (e.g. calling `Int.maxI32 + 1`),
## then the operation will *overflow* or *underflow*, respectively. ## then the operation will *overflow*. When an overflow occurs, the program will crash.
##
## When this happens:
##
## * In a development build, you'll get an assertion failure.
## * In a release build, you'll get [wrapping overflow](https://en.wikipedia.org/wiki/Integer_overflow), which is almost always a mathematically incorrect outcome for the requested operation. (If you actually want wrapping, because you're writing something like a hash function, use functions like #Int.addWrapping.)
## ##
## As such, it's very important to design your code not to exceed these bounds! ## As such, it's very important to design your code not to exceed these bounds!
## If you need to do math outside these bounds, consider using ## If you need to do math outside these bounds, consider using a larger numeric size.
## a different representation other than #Int. The reason #Int has these
## bounds is for performance reasons.
# Int size : Num [ @Int size ] # Int size : Num [ @Int size ]
## Arithmetic ## Arithmetic
@ -217,6 +212,11 @@ desc : Int a, Int a -> [ Eq, Lt, Gt ]
## TODO should we offer hash32 etc even if someday it has to do a hash64 and truncate? ## TODO should we offer hash32 etc even if someday it has to do a hash64 and truncate?
## ##
## This function can crash under these circumstances:
##
## * It receives a function, or any type that contains a function (for example a record, tag, or #List containing a function)
## * It receives an erroneous #Float (`NaN`, `Infinity`, or `-Infinity` - these values can only originate from hosts)
##
## CAUTION: This function may give different answers in future releases of Roc, ## CAUTION: This function may give different answers in future releases of Roc,
## so be aware that if you rely on the exact answer this gives today, your ## so be aware that if you rely on the exact answer this gives today, your
## code may break in a future Roc release. ## code may break in a future Roc release.
@ -227,13 +227,13 @@ hash64 : a -> U64
## The highest number that can be stored in an #I32 without overflowing its ## The highest number that can be stored in an #I32 without overflowing its
## available memory and crashing. ## available memory and crashing.
## ##
## Note that this is smaller than the positive version of #Int.lowestI32 ## Note that this is smaller than the positive version of #Int.minI32
## which means if you call #Num.abs on #Int.lowestI32, it will overflow and crash! ## which means if you call #Num.abs on #Int.minI32, it will overflow and crash!
highestI32 : I32 maxI32 : I32
## The lowest number that can be stored in an #I32 without overflowing its ## The min number that can be stored in an #I32 without overflowing its
## available memory and crashing. ## available memory and crashing.
## ##
## Note that the positive version of this number is this is larger than ## Note that the positive version of this number is this is larger than
## #Int.highestI32, which means if you call #Num.abs on #Int.lowestI32, it will overflow and crash! ## #Int.maxI32, which means if you call #Num.abs on #Int.minI32, it will overflow and crash!
lowest : I32 minI32 : I32

View file

@ -1,4 +1,6 @@
api Num provides Num, DivByZero..., neg, abs, add, sub, mul, isOdd, isEven, isPositive, isNegative, isZero interface Num
exposes [ Num, neg, abs, add, sub, mul, isOdd, isEven, isPositive, isNegative, isZero ]
imports []
## Types ## Types
@ -12,13 +14,43 @@ api Num provides Num, DivByZero..., neg, abs, add, sub, mul, isOdd, isEven, isPo
## ##
## The number 1.5 technically has the type `Num FloatingPoint`, so when you pass two of them to `Num.add`, the answer you get is `3.0 : Num FloatingPoint`. ## The number 1.5 technically has the type `Num FloatingPoint`, so when you pass two of them to `Num.add`, the answer you get is `3.0 : Num FloatingPoint`.
## ##
## The type #Float is defined to be an alias for `Num FloatingPoint`, so `3.0 : Num FloatingPoint` is the same answer as `3.0 : Float`. ## The type #Float is defined to be an alias for `Num FloatingPoint`, so `3.0 : Num FloatingPoint` is the same answer as `3.0 : Float`. # # Similarly, the number 1 technically has the type `Num Integer`, so when you pass two of them to `Num.add`, the answer you get is `2 : Num Integer`. # # The type #Int is defined to be an alias for `Num Integer`, so `2 : Num Integer` is the same answer as `2 : Int`. #
##
## Similarly, the number 1 technically has the type `Num Integer`, so when you pass two of them to `Num.add`, the answer you get is `2 : Num Integer`.
##
## The type #Int is defined to be an alias for `Num Integer`, so `2 : Num Integer` is the same answer as `2 : Int`.
##
## In this way, the `Num` type makes it possible to have `1 + 1` return `2 : Int` and `1.5 + 1.5` return `3.0 : Float`. ## In this way, the `Num` type makes it possible to have `1 + 1` return `2 : Int` and `1.5 + 1.5` return `3.0 : Float`.
##
## ## Number Literals
##
## Number literals without decimal points (like `0`, `4` or `360`)
## have the type `Num *` at first, but usually end up taking on
## a more specific type based on how they're used.
##
## For example, in `(1 + List.len myList)`, the `1` has the type `Num *` at first,
## but because `List.len` returns a `Ulen`, the `1` ends up changing from
## `Num *` to the more specific `Ulen`, and the expression as a whole
## ends up having the type `Ulen`.
##
## Sometimes number literals don't become more specific. For example,
## the #Num.toStr function has the type `Num * -> Str`. This means that
## when calling `Num.toStr (5 + 6)`, the expression `(5 + 6)`
## still has the type `Num *`. When this happens, `Num *` defaults to
## being an #I32 - so this addition expression would overflow
## if either 5 or 6 were replaced with a number big enough to cause
## addition overflow on an #I32.
##
## If this default of #I32 is not big enough for your purposes,
## you can add an `i64` to the end of the number literal, like so:
##
## >>> Num.toStr 5_000_000_000i64
##
## This `i64` suffix specifies that you want this number literal to be
## an #I64 instead of a `Num *`. All the other numeric types have
## suffixes just like `i64`; here are some other examples:
##
## * `215u8` is a `215` value of type #U8
## * `76.4f32` is a `76.4` value of type #F32
## * `12345ulen` is a `12345` value of type `#Ulen`
##
## In practice, these are rarely needed. It's most common to write
## number literals without any suffix.
Num range : @Num range Num range : @Num range
## Convert ## Convert
@ -137,3 +169,19 @@ sub : Num range, Num range -> Num range
## >>> Float.pi ## >>> Float.pi
## >>> |> Num.mul 2.0 ## >>> |> Num.mul 2.0
mul : Num range, Num range -> Num range mul : Num range, Num range -> Num range
## Convert
## Convert a number to a string, formatted as the traditional base 10 (decimal).
##
## >>> Num.toStr 42
##
## Only #Float values will show a decimal point, and they will always have one.
##
## >>> Num.toStr 4.2
##
## >>> Num.toStr 4.0
##
## For other bases see #toHexStr, #toOctalStr, and #toBinaryStr.
toStr : Num * -> Str

View file

@ -1,4 +1,4 @@
api Str provides Str, isEmpty, join interface Str exposes [ Str, isEmpty, join ] imports []
## Types ## Types
@ -6,8 +6,8 @@ api Str provides Str, isEmpty, join
## ##
## Dealing with text is deep topic, so by design, Roc's `Str` module sticks ## Dealing with text is deep topic, so by design, Roc's `Str` module sticks
## to the basics. For more advanced use cases like working with raw [code points](https://unicode.org/glossary/#code_point), ## to the basics. For more advanced use cases like working with raw [code points](https://unicode.org/glossary/#code_point),
## see the [roc/unicode](roc/unicode) package, and for locale-specific text ## see the [roc/unicode](roc/unicode) package. For locale-specific text
## functions (including capitalization, as capitalization rules vary by locale) ## functions (including capitalizing a string, as capitalization rules vary by locale)
## see the [roc/locale](roc/locale) package. ## see the [roc/locale](roc/locale) package.
## ##
## ### Unicode ## ### Unicode
@ -20,10 +20,10 @@ api Str provides Str, isEmpty, join
## * "🐦" ## * "🐦"
## ##
## Every Unicode string is a sequence of [grapheme clusters](https://unicode.org/glossary/#grapheme_cluster). ## Every Unicode string is a sequence of [grapheme clusters](https://unicode.org/glossary/#grapheme_cluster).
## A grapheme cluster corresponds to what a person reading a string might call ## A grapheme cluster corresponds to what a person reading a string might call a "character",
## a "character", but because the term "character" is used to mean many different ## but because the term "character" is used to mean many different concepts across
## concepts across different programming languages, we intentionally avoid it in Roc. ## different programming languages, the documentation for Roc strings intentionally
## Instead, we use the term "clusters" as a shorthand for "grapheme clusters." ## avoids it. Instead, we use the term "clusters" as a shorthand for "grapheme clusters."
## ##
## You can get the number of grapheme clusters in a string by calling `Str.countClusters` on it: ## You can get the number of grapheme clusters in a string by calling `Str.countClusters` on it:
## ##
@ -33,13 +33,53 @@ api Str provides Str, isEmpty, join
## ##
## >>> Str.countClusters "👍" ## >>> Str.countClusters "👍"
## ##
## > The `countClusters` function traverses the entire string to calculate its answer, ## > The `countClusters` function walks through the entire string to get its answer,
## > so it's much better for performance to use `Str.isEmpty` instead of ## > so if you want to check whether a string is empty, you'll get much better performance
## > calling `Str.countClusters` and checking whether the count was `0`. ## > by calling `Str.isEmpty myStr` instead of `Str.countClusters myStr == 0`.
## ##
## ### Escape characters ## ### Escape sequences
## ##
## ### String interpolation ## If you put a `\` in a Roc string literal, it begins an *escape sequence*.
## An escape sequence is a convenient way to insert certain strings into other strings.
## For example, suppose you write this Roc string:
##
## "I took the one less traveled by,\nAnd that has made all the difference."
##
## The `"\n"` in the middle will insert a line break into this string. There are
## other ways of getting a line break in there, but `"\n"` is the most common.
##
## Another way you could insert a newlines is by writing `\u{0x0A}` instead of `\n`.
## That would result in the same string, because the `\u` escape sequence inserts
## [Unicode code points](https://unicode.org/glossary/#code_point) directly into
## the string. The Unicode code point 10 is a newline, and 10 is `0A` in hexadecimal.
## `0x0A` is a Roc hexadecimal literal, and `\u` escape sequences are always
## followed by a hexadecimal literal inside `{` and `}` like this.
##
## As another example, `"R\u{0x6F}c"` is the same string as `"Roc"`, because
## `"\u{0x6F}"` corresponds to the Unicode code point for lowercase `o`. If you
## want to [spice things up a bit](https://en.wikipedia.org/wiki/Metal_umlaut),
## you can write `"R\u{0xF6}c"` as an alternative way to get the string `"Röc"\.
##
## Roc strings also support these escape sequences:
##
## * `\\` - an actual backslash (writing a single `\` always begins an escape sequence!)
## * `\"` - an actual quotation mark (writing a `"` without a `\` ends the string)
## * `\r` - [carriage return](https://en.wikipedia.org/wiki/Carriage_Return)
## * `\t` - [horizontal tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
## * `\v` - [vertical tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
##
## You can also use escape sequences to insert named strings into other strings, like so:
##
## name = "Lee"
## city = "Roctown"
##
## greeting = "Hello there, \(name)! Welcome to \(city)."
##
## Here, `greeting` will become the string `"Hello there, Lee! Welcome to Roctown."`.
## This is known as [string interpolation](https://en.wikipedia.org/wiki/String_interpolation),
## and you can use it as many times as you like inside a string. The name
## between the parentheses must refer to a `Str` value that is currently in
## scope, and it must be a name - it can't be an arbitrary expression like a function call.
## ##
## ### Encoding ## ### Encoding
## ##
@ -47,7 +87,8 @@ api Str provides Str, isEmpty, join
## [encoding](https://en.wikipedia.org/wiki/Character_encoding). As it happens, ## [encoding](https://en.wikipedia.org/wiki/Character_encoding). As it happens,
## they are currently encoded in UTF-8, but this module is intentionally designed ## they are currently encoded in UTF-8, but this module is intentionally designed
## not to rely on that implementation detail so that a future release of Roc can ## not to rely on that implementation detail so that a future release of Roc can
## potentially change it without breaking existing Roc applications. ## potentially change it without breaking existing Roc applications. (UTF-8
## seems pretty great today, but so did UTF-16 at an earlier point in history.)
## ##
## This module has functions to can convert a #Str to a #List of raw [code unit](https://unicode.org/glossary/#code_unit) ## This module has functions to can convert a #Str to a #List of raw [code unit](https://unicode.org/glossary/#code_unit)
## integers (not to be confused with the [code points](https://unicode.org/glossary/#code_point) ## integers (not to be confused with the [code points](https://unicode.org/glossary/#code_point)
@ -63,29 +104,29 @@ Str : [ @Str ]
## Since #Float values are imprecise, it's usually best to limit this to the lowest ## Since #Float values are imprecise, it's usually best to limit this to the lowest
## number you can choose that will make sense for what you want to display. ## number you can choose that will make sense for what you want to display.
## ##
## If you want to kep all the digits, passing #Int.highestSupported will accomplish this, ## If you want to keep all the digits, passing the same float to #Str.num
## but it's recommended to pass much smaller numbers instead. ## will do that.
## decimal : Float *, Ulen -> Str
## Passing a negative number for decimal places is equivalent to passing 0.
decimal : Float *, ULen -> Str
## Convert an #Int to a string.
int : Int * -> Str
## Split a string around a separator. ## Split a string around a separator.
## ##
## >>> Str.splitClusters "1,2,3" "," ## >>> Str.split "1,2,3" ","
## ##
## Passing `""` for the separator is not useful; it returns the original string ## Passing `""` for the separator is not useful; it returns the original string
## wrapped in a list. ## wrapped in a list.
## ##
## >>> Str.splitClusters "1,2,3" "" ## >>> Str.split "1,2,3" ""
## ##
## To split a string into its grapheme clusters, use #Str.clusters ## To split a string into its individual grapheme clusters, use #Str.clusters
split : Str, Str -> List Str split : Str, Str -> List Str
## Check ## Check
## Returns #True if the string is empty, and #False otherwise.
##
## >>> Str.isEmpty "hi!"
##
## >>> Str.isEmpty ""
isEmpty : Str -> Bool isEmpty : Str -> Bool
startsWith : Str, Str -> Bool startsWith : Str, Str -> Bool
@ -94,9 +135,9 @@ endsWith : Str, Str -> Bool
contains : Str, Str -> Bool contains : Str, Str -> Bool
any : Str, Str -> Bool anyClusters : Str, (Str -> Bool) -> Bool
all : Str, Str -> Bool allClusters : Str, (Str -> Bool) -> Bool
## Combine ## Combine
@ -111,14 +152,33 @@ join : List Str -> Str
## >>> Str.joinWith [ "one", "two", "three" ] ", " ## >>> Str.joinWith [ "one", "two", "three" ] ", "
joinWith : List Str, Str -> Str joinWith : List Str, Str -> Str
## Add to the start of a string until it has at least the given number of
## grapheme clusters.
##
## >>> Str.padClustersStart "0" 5 "36"
##
## >>> Str.padClustersStart "0" 1 "36"
##
## >>> Str.padClustersStart "0" 5 "12345"
##
## >>> Str.padClustersStart "✈️"" 5 "👩‍👩‍👦‍👦👩‍👩‍👦‍👦👩‍👩‍👦‍👦"
padClustersStart : Str, Int, Str -> Str
padStart : Str, Int, Str -> Str ## Add to the end of a string until it has at least the given number of
## grapheme clusters.
padEnd : Str, Int, Str -> Str ##
## >>> Str.padClustersStart "0" 5 "36"
##
## >>> Str.padClustersStart "0" 1 "36"
##
## >>> Str.padClustersStart "0" 5 "12345"
##
## >>> Str.padClustersStart "✈️"" 5 "👩‍👩‍👦‍👦👩‍👩‍👦‍👦👩‍👩‍👦‍👦"
padClustersEnd : Str, Int, Str -> Str
## Grapheme Clusters ## Grapheme Clusters
## Split a string into its grapheme clusters. ## Split a string into its individual grapheme clusters.
## ##
## >>> Str.clusters "1,2,3" ## >>> Str.clusters "1,2,3"
## ##
@ -126,6 +186,11 @@ padEnd : Str, Int, Str -> Str
## ##
clusters : Str -> List Str clusters : Str -> List Str
## Reverse the order of the string's individual grapheme clusters.
##
## >>> Str.reverseClusters "1-2-3"
##
## >>> Str.reverseClusters "🐦✈️"👩‍👩‍👦‍👦"
reverseClusters : Str -> Str reverseClusters : Str -> Str
foldClusters : Str, { start: state, step: (state, Str -> state) } -> state foldClusters : Str, { start: state, step: (state, Str -> state) } -> state
@ -306,6 +371,3 @@ foldRevUtf16 : Str, { start: state, step: (state, U16 -> state) } -> state
## ##
## To convert a #Str into a plain `List U32` of UTF-32 code units, see #Str.toUtf32. ## To convert a #Str into a plain `List U32` of UTF-32 code units, see #Str.toUtf32.
foldRevUtf32 : Str, { start: state, step: (state, U32 -> state) } -> state foldRevUtf32 : Str, { start: state, step: (state, U32 -> state) } -> state

View file

@ -0,0 +1,51 @@
use roc_collections::all::{default_hasher, MutMap};
use roc_module::symbol::{Interns, Symbol};
use roc_mono::layout::Layout;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LayoutId(u32);
impl LayoutId {
// Returns something like "foo#1" when given a symbol that interns to "foo"
// and a LayoutId of 1.
pub fn to_symbol_string(self, symbol: Symbol, interns: &Interns) -> String {
format!("{}#{}", symbol.ident_string(interns), self.0)
}
}
struct IdsByLayout<'a> {
by_id: MutMap<Layout<'a>, u32>,
next_id: u32,
}
#[derive(Default)]
pub struct LayoutIds<'a> {
by_symbol: MutMap<Symbol, IdsByLayout<'a>>,
}
impl<'a> LayoutIds<'a> {
/// Returns a LayoutId which is unique for the given symbol and layout.
/// If given the same symbol and same layout, returns the same LayoutId.
pub fn get(&mut self, symbol: Symbol, layout: &Layout<'a>) -> LayoutId {
// Note: this function does some weird stuff to satisfy the borrow checker.
// There's probably a nicer way to write it that still works.
let ids = self.by_symbol.entry(symbol).or_insert_with(|| IdsByLayout {
by_id: HashMap::with_capacity_and_hasher(1, default_hasher()),
next_id: 1,
});
// Get the id associated with this layout, or default to next_id.
let answer = ids.by_id.get(layout).copied().unwrap_or(ids.next_id);
// If we had to default to next_id, it must not have been found;
// store the ID we're going to return and increment next_id.
if answer == ids.next_id {
ids.by_id.insert(layout.clone(), ids.next_id);
ids.next_id += 1;
}
LayoutId(answer)
}
}

View file

@ -11,4 +11,5 @@
// re-enable this when working on performance optimizations than have it block PRs. // re-enable this when working on performance optimizations than have it block PRs.
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant)]
pub mod layout_id;
pub mod llvm; pub mod llvm;

View file

@ -1,3 +1,4 @@
use crate::layout_id::LayoutIds;
use crate::llvm::convert::{ use crate::llvm::convert::{
basic_type_from_layout, collection, get_fn_type, get_ptr_type, ptr_int, basic_type_from_layout, collection, get_fn_type, get_ptr_type, ptr_int,
}; };
@ -15,7 +16,7 @@ use inkwell::AddressSpace;
use inkwell::{FloatPredicate, IntPredicate, OptimizationLevel}; use inkwell::{FloatPredicate, IntPredicate, OptimizationLevel};
use roc_collections::all::ImMap; use roc_collections::all::ImMap;
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
use roc_mono::expr::{Expr, Proc, Procs}; use roc_mono::expr::{Expr, Proc};
use roc_mono::layout::{Builtin, Layout}; use roc_mono::layout::{Builtin, Layout};
use target_lexicon::CallingConvention; use target_lexicon::CallingConvention;
@ -144,10 +145,10 @@ pub fn add_passes(fpm: &PassManager<FunctionValue<'_>>, opt_level: OptLevel) {
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
pub fn build_expr<'a, 'ctx, 'env>( pub fn build_expr<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
scope: &Scope<'a, 'ctx>, scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
expr: &'a Expr<'a>, expr: &Expr<'a>,
procs: &Procs<'a>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
use roc_mono::expr::Expr::*; use roc_mono::expr::Expr::*;
@ -166,14 +167,53 @@ pub fn build_expr<'a, 'ctx, 'env>(
let pass = env.arena.alloc(Expr::Store(pass_stores, pass_expr)); let pass = env.arena.alloc(Expr::Store(pass_stores, pass_expr));
let fail = env.arena.alloc(Expr::Store(fail_stores, fail_expr)); let fail = env.arena.alloc(Expr::Store(fail_stores, fail_expr));
let conditional = Branch2 { let ret_type =
cond: branch_symbol, basic_type_from_layout(env.arena, env.context, &ret_layout, env.ptr_bytes);
pass,
fail,
ret_layout: ret_layout.clone(),
};
build_branch2(env, scope, parent, conditional, procs) let cond_expr = load_symbol(env, scope, branch_symbol);
match cond_expr {
IntValue(value) => {
// This is a call tobuild_basic_phi2, except inlined to prevent
// problems with lifetimes and closures involving layout_ids.
let builder = env.builder;
let context = env.context;
// build blocks
let then_block = context.append_basic_block(parent, "then");
let else_block = context.append_basic_block(parent, "else");
let cont_block = context.append_basic_block(parent, "branchcont");
builder.build_conditional_branch(value, then_block, else_block);
// build then block
builder.position_at_end(then_block);
let then_val = build_expr(env, layout_ids, scope, parent, pass);
builder.build_unconditional_branch(cont_block);
let then_block = builder.get_insert_block().unwrap();
// build else block
builder.position_at_end(else_block);
let else_val = build_expr(env, layout_ids, scope, parent, fail);
builder.build_unconditional_branch(cont_block);
let else_block = builder.get_insert_block().unwrap();
// emit merge block
builder.position_at_end(cont_block);
let phi = builder.build_phi(ret_type, "branch");
phi.add_incoming(&[(&then_val, then_block), (&else_val, else_block)]);
phi.as_basic_value()
}
_ => panic!(
"Tried to make a branch out of an invalid condition: cond_expr = {:?}",
cond_expr,
),
}
} }
Switch { Switch {
cond, cond,
@ -201,14 +241,14 @@ pub fn build_expr<'a, 'ctx, 'env>(
ret_type, ret_type,
}; };
build_switch(env, scope, parent, switch_args, procs) build_switch(env, layout_ids, scope, parent, switch_args)
} }
Store(stores, ret) => { Store(stores, ret) => {
let mut scope = im_rc::HashMap::clone(scope); let mut scope = im_rc::HashMap::clone(scope);
let context = &env.context; let context = &env.context;
for (symbol, layout, expr) in stores.iter() { for (symbol, layout, expr) in stores.iter() {
let val = build_expr(env, &scope, parent, &expr, procs); let val = build_expr(env, layout_ids, &scope, parent, &expr);
let expr_bt = basic_type_from_layout(env.arena, context, &layout, env.ptr_bytes); let expr_bt = basic_type_from_layout(env.arena, context, &layout, env.ptr_bytes);
let alloca = create_entry_block_alloca( let alloca = create_entry_block_alloca(
env, env,
@ -229,16 +269,17 @@ pub fn build_expr<'a, 'ctx, 'env>(
scope.insert(*symbol, (layout.clone(), alloca)); scope.insert(*symbol, (layout.clone(), alloca));
} }
build_expr(env, &scope, parent, ret, procs) build_expr(env, layout_ids, &scope, parent, ret)
} }
CallByName(symbol, args) => match *symbol { CallByName { name, layout, args } => match *name {
Symbol::BOOL_OR => { Symbol::BOOL_OR => {
// The (||) operator // The (||) operator
debug_assert!(args.len() == 2); debug_assert!(args.len() == 2);
let comparison = build_expr(env, scope, parent, &args[0].0, procs).into_int_value(); let comparison =
build_expr(env, layout_ids, scope, parent, &args[0].0).into_int_value();
let build_then = || env.context.bool_type().const_int(true as u64, false).into(); let build_then = || env.context.bool_type().const_int(true as u64, false).into();
let build_else = || build_expr(env, scope, parent, &args[1].0, procs); let build_else = || build_expr(env, layout_ids, scope, parent, &args[1].0);
let ret_type = env.context.bool_type().into(); let ret_type = env.context.bool_type().into();
@ -248,8 +289,9 @@ pub fn build_expr<'a, 'ctx, 'env>(
// The (&&) operator // The (&&) operator
debug_assert!(args.len() == 2); debug_assert!(args.len() == 2);
let comparison = build_expr(env, scope, parent, &args[0].0, procs).into_int_value(); let comparison =
let build_then = || build_expr(env, scope, parent, &args[1].0, procs); build_expr(env, layout_ids, scope, parent, &args[0].0).into_int_value();
let build_then = || build_expr(env, layout_ids, scope, parent, &args[1].0);
let build_else = || { let build_else = || {
env.context env.context
.bool_type() .bool_type()
@ -265,7 +307,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
// The (!) operator // The (!) operator
debug_assert!(args.len() == 1); debug_assert!(args.len() == 1);
let arg = build_expr(env, scope, parent, &args[0].0, procs); let arg = build_expr(env, layout_ids, scope, parent, &args[0].0);
let int_val = env.builder.build_not(arg.into_int_value(), "bool_not"); let int_val = env.builder.build_not(arg.into_int_value(), "bool_not");
@ -275,17 +317,27 @@ pub fn build_expr<'a, 'ctx, 'env>(
let mut arg_tuples: Vec<(BasicValueEnum, &'a Layout<'a>)> = let mut arg_tuples: Vec<(BasicValueEnum, &'a Layout<'a>)> =
Vec::with_capacity_in(args.len(), env.arena); Vec::with_capacity_in(args.len(), env.arena);
for (arg, layout) in args.iter() { for (arg, arg_layout) in args.iter() {
arg_tuples.push((build_expr(env, scope, parent, arg, procs), layout)); arg_tuples.push((build_expr(env, layout_ids, scope, parent, arg), arg_layout));
} }
call_with_args(*symbol, parent, arg_tuples.into_bump_slice(), env) call_with_args(
env,
layout_ids,
layout,
*name,
parent,
arg_tuples.into_bump_slice(),
)
} }
}, },
FunctionPointer(symbol) => { FunctionPointer(symbol, layout) => {
let fn_name = layout_ids
.get(*symbol, layout)
.to_symbol_string(*symbol, &env.interns);
let ptr = env let ptr = env
.module .module
.get_function(symbol.ident_string(&env.interns)) .get_function(fn_name.as_str())
.unwrap_or_else(|| panic!("Could not get pointer to unknown function {:?}", symbol)) .unwrap_or_else(|| panic!("Could not get pointer to unknown function {:?}", symbol))
.as_global_value() .as_global_value()
.as_pointer_value(); .as_pointer_value();
@ -296,10 +348,10 @@ pub fn build_expr<'a, 'ctx, 'env>(
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), env.arena); let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), env.arena);
for arg in args.iter() { for arg in args.iter() {
arg_vals.push(build_expr(env, scope, parent, arg, procs)); arg_vals.push(build_expr(env, layout_ids, scope, parent, arg));
} }
let call = match build_expr(env, scope, parent, sub_expr, procs) { let call = match build_expr(env, layout_ids, scope, parent, sub_expr) {
BasicValueEnum::PointerValue(ptr) => { BasicValueEnum::PointerValue(ptr) => {
env.builder.build_call(ptr, arg_vals.as_slice(), "tmp") env.builder.build_call(ptr, arg_vals.as_slice(), "tmp")
} }
@ -393,7 +445,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
let index_val = ctx.i32_type().const_int(index as u64, false); let index_val = ctx.i32_type().const_int(index as u64, false);
let elem_ptr = let elem_ptr =
unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") }; unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") };
let val = build_expr(env, &scope, parent, &elem, procs); let val = build_expr(env, layout_ids, &scope, parent, &elem);
builder.build_store(elem_ptr, val); builder.build_store(elem_ptr, val);
} }
@ -439,7 +491,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
let mut field_vals = Vec::with_capacity_in(num_fields, env.arena); let mut field_vals = Vec::with_capacity_in(num_fields, env.arena);
for (field_expr, field_layout) in sorted_fields.iter() { for (field_expr, field_layout) in sorted_fields.iter() {
let val = build_expr(env, &scope, parent, field_expr, procs); let val = build_expr(env, layout_ids, &scope, parent, field_expr);
let field_type = let field_type =
basic_type_from_layout(env.arena, env.context, &field_layout, env.ptr_bytes); basic_type_from_layout(env.arena, env.context, &field_layout, env.ptr_bytes);
@ -476,7 +528,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
let mut field_vals = Vec::with_capacity_in(num_fields, env.arena); let mut field_vals = Vec::with_capacity_in(num_fields, env.arena);
for (field_expr, field_layout) in it { for (field_expr, field_layout) in it {
let val = build_expr(env, &scope, parent, field_expr, procs); let val = build_expr(env, layout_ids, &scope, parent, field_expr);
let field_type = let field_type =
basic_type_from_layout(env.arena, env.context, &field_layout, env.ptr_bytes); basic_type_from_layout(env.arena, env.context, &field_layout, env.ptr_bytes);
@ -516,7 +568,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
let mut field_vals = Vec::with_capacity_in(num_fields, env.arena); let mut field_vals = Vec::with_capacity_in(num_fields, env.arena);
for (field_expr, field_layout) in arguments.iter() { for (field_expr, field_layout) in arguments.iter() {
let val = build_expr(env, &scope, parent, field_expr, procs); let val = build_expr(env, layout_ids, &scope, parent, field_expr);
let field_type = let field_type =
basic_type_from_layout(env.arena, env.context, &field_layout, ptr_size); basic_type_from_layout(env.arena, env.context, &field_layout, ptr_size);
@ -594,7 +646,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
// Get Struct val // Get Struct val
// Since this is a one-element tag union, we get the correct struct immediately // Since this is a one-element tag union, we get the correct struct immediately
let argument = build_expr(env, &scope, parent, expr, procs).into_struct_value(); let argument = build_expr(env, layout_ids, &scope, parent, expr).into_struct_value();
builder builder
.build_extract_value( .build_extract_value(
@ -630,7 +682,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
.struct_type(field_types.into_bump_slice(), false); .struct_type(field_types.into_bump_slice(), false);
// cast the argument bytes into the desired shape for this tag // cast the argument bytes into the desired shape for this tag
let argument = build_expr(env, &scope, parent, expr, procs).into_struct_value(); let argument = build_expr(env, layout_ids, &scope, parent, expr).into_struct_value();
let struct_value = cast_struct_struct(builder, argument, struct_type); let struct_value = cast_struct_struct(builder, argument, struct_type);
@ -705,41 +757,6 @@ fn extract_tag_discriminant<'a, 'ctx, 'env>(
.into_int_value() .into_int_value()
} }
struct Branch2<'a> {
cond: &'a Symbol,
pass: &'a Expr<'a>,
fail: &'a Expr<'a>,
ret_layout: Layout<'a>,
}
fn build_branch2<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
cond: Branch2<'a>,
procs: &Procs<'a>,
) -> BasicValueEnum<'ctx> {
let ret_layout = cond.ret_layout;
let pass = cond.pass;
let fail = cond.fail;
let ret_type = basic_type_from_layout(env.arena, env.context, &ret_layout, env.ptr_bytes);
let cond_expr = load_symbol(env, scope, cond.cond);
match cond_expr {
IntValue(value) => {
let build_then = || build_expr(env, scope, parent, pass, procs);
let build_else = || build_expr(env, scope, parent, fail, procs);
build_basic_phi2(env, parent, value, build_then, build_else, ret_type)
}
_ => panic!(
"Tried to make a branch out of an invalid condition: cond_expr = {:?}",
cond_expr,
),
}
}
struct SwitchArgs<'a, 'ctx> { struct SwitchArgs<'a, 'ctx> {
pub cond_expr: &'a Expr<'a>, pub cond_expr: &'a Expr<'a>,
pub cond_layout: Layout<'a>, pub cond_layout: Layout<'a>,
@ -750,10 +767,10 @@ struct SwitchArgs<'a, 'ctx> {
fn build_switch<'a, 'ctx, 'env>( fn build_switch<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
scope: &Scope<'a, 'ctx>, scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
switch_args: SwitchArgs<'a, 'ctx>, switch_args: SwitchArgs<'a, 'ctx>,
procs: &Procs<'a>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let arena = env.arena; let arena = env.arena;
let builder = env.builder; let builder = env.builder;
@ -774,7 +791,7 @@ fn build_switch<'a, 'ctx, 'env>(
Layout::Builtin(Builtin::Float64) => { Layout::Builtin(Builtin::Float64) => {
// float matches are done on the bit pattern // float matches are done on the bit pattern
cond_layout = Layout::Builtin(Builtin::Int64); cond_layout = Layout::Builtin(Builtin::Int64);
let full_cond = build_expr(env, scope, parent, cond_expr, procs); let full_cond = build_expr(env, layout_ids, scope, parent, cond_expr);
builder builder
.build_bitcast(full_cond, env.context.i64_type(), "") .build_bitcast(full_cond, env.context.i64_type(), "")
@ -783,11 +800,14 @@ fn build_switch<'a, 'ctx, 'env>(
Layout::Union(_) => { Layout::Union(_) => {
// we match on the discriminant, not the whole Tag // we match on the discriminant, not the whole Tag
cond_layout = Layout::Builtin(Builtin::Int64); cond_layout = Layout::Builtin(Builtin::Int64);
let full_cond = build_expr(env, scope, parent, cond_expr, procs).into_struct_value(); let full_cond =
build_expr(env, layout_ids, scope, parent, cond_expr).into_struct_value();
extract_tag_discriminant(env, full_cond) extract_tag_discriminant(env, full_cond)
} }
Layout::Builtin(_) => build_expr(env, scope, parent, cond_expr, procs).into_int_value(), Layout::Builtin(_) => {
build_expr(env, layout_ids, scope, parent, cond_expr).into_int_value()
}
other => todo!("Build switch value from layout: {:?}", other), other => todo!("Build switch value from layout: {:?}", other),
}; };
@ -824,7 +844,7 @@ fn build_switch<'a, 'ctx, 'env>(
for ((_, branch_expr), (_, block)) in branches.iter().zip(cases) { for ((_, branch_expr), (_, block)) in branches.iter().zip(cases) {
builder.position_at_end(block); builder.position_at_end(block);
let branch_val = build_expr(env, scope, parent, branch_expr, procs); let branch_val = build_expr(env, layout_ids, scope, parent, branch_expr);
builder.build_unconditional_branch(cont_block); builder.build_unconditional_branch(cont_block);
@ -834,7 +854,7 @@ fn build_switch<'a, 'ctx, 'env>(
// The block for the conditional's default branch. // The block for the conditional's default branch.
builder.position_at_end(default_block); builder.position_at_end(default_block);
let default_val = build_expr(env, scope, parent, default_branch, procs); let default_val = build_expr(env, layout_ids, scope, parent, default_branch);
builder.build_unconditional_branch(cont_block); builder.build_unconditional_branch(cont_block);
@ -852,40 +872,17 @@ fn build_switch<'a, 'ctx, 'env>(
phi.as_basic_value() phi.as_basic_value()
} }
// TODO trim down these arguments
#[allow(dead_code)]
#[allow(clippy::too_many_arguments)]
fn build_expr_phi2<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
comparison: IntValue<'ctx>,
pass: &'a Expr<'a>,
fail: &'a Expr<'a>,
ret_type: BasicTypeEnum<'ctx>,
procs: &Procs<'a>,
) -> BasicValueEnum<'ctx> {
build_basic_phi2(
env,
parent,
comparison,
|| build_expr(env, scope, parent, pass, procs),
|| build_expr(env, scope, parent, fail, procs),
ret_type,
)
}
fn build_basic_phi2<'a, 'ctx, 'env, PassFn, FailFn>( fn build_basic_phi2<'a, 'ctx, 'env, PassFn, FailFn>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
comparison: IntValue<'ctx>, comparison: IntValue<'ctx>,
build_pass: PassFn, mut build_pass: PassFn,
build_fail: FailFn, mut build_fail: FailFn,
ret_type: BasicTypeEnum<'ctx>, ret_type: BasicTypeEnum<'ctx>,
) -> BasicValueEnum<'ctx> ) -> BasicValueEnum<'ctx>
where where
PassFn: Fn() -> BasicValueEnum<'ctx>, PassFn: FnMut() -> BasicValueEnum<'ctx>,
FailFn: Fn() -> BasicValueEnum<'ctx>, FailFn: FnMut() -> BasicValueEnum<'ctx>,
{ {
let builder = env.builder; let builder = env.builder;
let context = env.context; let context = env.context;
@ -953,7 +950,9 @@ pub fn create_entry_block_alloca<'a, 'ctx>(
pub fn build_proc_header<'a, 'ctx, 'env>( pub fn build_proc_header<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
symbol: Symbol, symbol: Symbol,
layout: &Layout<'a>,
proc: &Proc<'a>, proc: &Proc<'a>,
) -> (FunctionValue<'ctx>, Vec<'a, BasicTypeEnum<'ctx>>) { ) -> (FunctionValue<'ctx>, Vec<'a, BasicTypeEnum<'ctx>>) {
let args = proc.args; let args = proc.args;
@ -972,11 +971,12 @@ pub fn build_proc_header<'a, 'ctx, 'env>(
let fn_type = get_fn_type(&ret_type, &arg_basic_types); let fn_type = get_fn_type(&ret_type, &arg_basic_types);
let fn_val = env.module.add_function( let fn_name = layout_ids
symbol.ident_string(&env.interns), .get(symbol, layout)
fn_type, .to_symbol_string(symbol, &env.interns);
Some(Linkage::Private), let fn_val = env
); .module
.add_function(fn_name.as_str(), fn_type, Some(Linkage::Private));
fn_val.set_call_conventions(fn_val.get_call_conventions()); fn_val.set_call_conventions(fn_val.get_call_conventions());
@ -985,8 +985,8 @@ pub fn build_proc_header<'a, 'ctx, 'env>(
pub fn build_proc<'a, 'ctx, 'env>( pub fn build_proc<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
proc: Proc<'a>, proc: Proc<'a>,
procs: &Procs<'a>,
fn_val: FunctionValue<'ctx>, fn_val: FunctionValue<'ctx>,
arg_basic_types: Vec<'a, BasicTypeEnum<'ctx>>, arg_basic_types: Vec<'a, BasicTypeEnum<'ctx>>,
) { ) {
@ -1015,7 +1015,7 @@ pub fn build_proc<'a, 'ctx, 'env>(
scope.insert(*arg_symbol, (layout.clone(), alloca)); scope.insert(*arg_symbol, (layout.clone(), alloca));
} }
let body = build_expr(env, &scope, fn_val, &proc.body, procs); let body = build_expr(env, layout_ids, &scope, fn_val, &proc.body);
builder.build_return(Some(&body)); builder.build_return(Some(&body));
} }
@ -1033,10 +1033,12 @@ pub fn verify_fn(fn_val: FunctionValue<'_>) {
#[inline(always)] #[inline(always)]
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
fn call_with_args<'a, 'ctx, 'env>( fn call_with_args<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
symbol: Symbol, symbol: Symbol,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)], args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)],
env: &Env<'a, 'ctx, 'env>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
match symbol { match symbol {
Symbol::INT_ADD | Symbol::NUM_ADD => { Symbol::INT_ADD | Symbol::NUM_ADD => {
@ -1396,9 +1398,12 @@ fn call_with_args<'a, 'ctx, 'env>(
BasicValueEnum::IntValue(int_val) BasicValueEnum::IntValue(int_val)
} }
_ => { _ => {
let fn_name = layout_ids
.get(symbol, layout)
.to_symbol_string(symbol, &env.interns);
let fn_val = env let fn_val = env
.module .module
.get_function(symbol.ident_string(&env.interns)) .get_function(fn_name.as_str())
.unwrap_or_else(|| panic!("Unrecognized function: {:?}", symbol)); .unwrap_or_else(|| panic!("Unrecognized function: {:?}", symbol));
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), env.arena); let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), env.arena);

View file

@ -38,7 +38,7 @@ macro_rules! assert_llvm_evals_to {
fpm.initialize(); fpm.initialize();
// Compute main_fn_type before moving subs to Env // Compute main_fn_type before moving subs to Env
let layout = Layout::from_content(&arena, content, &subs, ptr_bytes) let layout = Layout::new(&arena, content, &subs, ptr_bytes)
.unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs)); .unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs));
let execution_engine = let execution_engine =
module module
@ -60,6 +60,7 @@ macro_rules! assert_llvm_evals_to {
}; };
let mut procs = Procs::default(); let mut procs = Procs::default();
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
let mut layout_ids = roc_gen::layout_id::LayoutIds::default();
// Populate Procs and get the low-level Expr from the canonical Expr // Populate Procs and get the low-level Expr from the canonical Expr
let mut mono_problems = Vec::new(); let mut mono_problems = Vec::new();
@ -78,13 +79,16 @@ macro_rules! assert_llvm_evals_to {
env.interns.all_ident_ids.insert(home, ident_ids); env.interns.all_ident_ids.insert(home, ident_ids);
let mut headers = Vec::with_capacity(procs.len()); let mut headers = Vec::with_capacity(procs.len());
let (mut proc_map, runtime_errors) = procs.into_map();
assert_eq!(runtime_errors, roc_collections::all::MutSet::default());
// Add all the Proc headers to the module. // Add all the Proc headers to the module.
// We have to do this in a separate pass first, // We have to do this in a separate pass first,
// because their bodies may reference each other. // because their bodies may reference each other.
for (symbol, opt_proc) in procs.as_map().into_iter() { for (symbol, mut procs_by_layout) in proc_map.drain() {
if let Some(proc) = opt_proc { for (layout, proc) in procs_by_layout.drain() {
let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc); let (fn_val, arg_basic_types) = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
headers.push((proc, fn_val, arg_basic_types)); headers.push((proc, fn_val, arg_basic_types));
} }
@ -96,7 +100,7 @@ macro_rules! assert_llvm_evals_to {
// (This approach means we don't have to defensively clone name here.) // (This approach means we don't have to defensively clone name here.)
// //
// println!("\n\nBuilding and then verifying function {}\n\n", name); // println!("\n\nBuilding and then verifying function {}\n\n", name);
build_proc(&env, proc, &procs, fn_val, arg_basic_types); build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types);
if fn_val.verify(true) { if fn_val.verify(true) {
fpm.run_on(&fn_val); fpm.run_on(&fn_val);
@ -119,10 +123,10 @@ macro_rules! assert_llvm_evals_to {
let ret = roc_gen::llvm::build::build_expr( let ret = roc_gen::llvm::build::build_expr(
&env, &env,
&mut layout_ids,
&ImMap::default(), &ImMap::default(),
main_fn, main_fn,
&main_body, &main_body,
&mut Procs::default(),
); );
builder.build_return(Some(&ret)); builder.build_return(Some(&ret));
@ -198,7 +202,7 @@ macro_rules! assert_opt_evals_to {
fpm.initialize(); fpm.initialize();
// Compute main_fn_type before moving subs to Env // Compute main_fn_type before moving subs to Env
let layout = Layout::from_content(&arena, content, &subs, ptr_bytes) let layout = Layout::new(&arena, content, &subs, ptr_bytes)
.unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs)); .unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs));
let execution_engine = let execution_engine =
@ -221,6 +225,7 @@ macro_rules! assert_opt_evals_to {
}; };
let mut procs = Procs::default(); let mut procs = Procs::default();
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
let mut layout_ids = roc_gen::layout_id::LayoutIds::default();
// Populate Procs and get the low-level Expr from the canonical Expr // Populate Procs and get the low-level Expr from the canonical Expr
let mut mono_problems = Vec::new(); let mut mono_problems = Vec::new();
@ -239,13 +244,16 @@ macro_rules! assert_opt_evals_to {
env.interns.all_ident_ids.insert(home, ident_ids); env.interns.all_ident_ids.insert(home, ident_ids);
let mut headers = Vec::with_capacity(procs.len()); let mut headers = Vec::with_capacity(procs.len());
let (mut proc_map, runtime_errors) = procs.into_map();
assert_eq!(runtime_errors, roc_collections::all::MutSet::default());
// Add all the Proc headers to the module. // Add all the Proc headers to the module.
// We have to do this in a separate pass first, // We have to do this in a separate pass first,
// because their bodies may reference each other. // because their bodies may reference each other.
for (symbol, opt_proc) in procs.as_map().into_iter() { for (symbol, mut procs_by_layout) in proc_map.drain() {
if let Some(proc) = opt_proc { for (layout, proc) in procs_by_layout.drain() {
let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc); let (fn_val, arg_basic_types) = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
headers.push((proc, fn_val, arg_basic_types)); headers.push((proc, fn_val, arg_basic_types));
} }
@ -257,7 +265,7 @@ macro_rules! assert_opt_evals_to {
// (This approach means we don't have to defensively clone name here.) // (This approach means we don't have to defensively clone name here.)
// //
// println!("\n\nBuilding and then verifying function {}\n\n", name); // println!("\n\nBuilding and then verifying function {}\n\n", name);
build_proc(&env, proc, &procs, fn_val, arg_basic_types); build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types);
if fn_val.verify(true) { if fn_val.verify(true) {
fpm.run_on(&fn_val); fpm.run_on(&fn_val);
@ -280,10 +288,10 @@ macro_rules! assert_opt_evals_to {
let ret = roc_gen::llvm::build::build_expr( let ret = roc_gen::llvm::build::build_expr(
&env, &env,
&mut layout_ids,
&ImMap::default(), &ImMap::default(),
main_fn, main_fn,
&main_body, &main_body,
&mut Procs::default(),
); );
builder.build_return(Some(&ret)); builder.build_return(Some(&ret));
@ -354,7 +362,7 @@ macro_rules! emit_expr {
fpm.initialize(); fpm.initialize();
// Compute main_fn_type before moving subs to Env // Compute main_fn_type before moving subs to Env
let layout = Layout::from_content(&arena, content, &subs, ptr_bytes) let layout = Layout::new(&arena, content, &subs, ptr_bytes)
.unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs)); .unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs));
let execution_engine = let execution_engine =

View file

@ -48,6 +48,10 @@ impl Symbol {
IdentId((self.0 >> 32) as u32) IdentId((self.0 >> 32) as u32)
} }
pub fn is_builtin(self) -> bool {
self.module_id().is_builtin()
}
pub fn module_string<'a>(&self, interns: &'a Interns) -> &'a InlinableString { pub fn module_string<'a>(&self, interns: &'a Interns) -> &'a InlinableString {
interns interns
.module_ids .module_ids

View file

@ -1203,14 +1203,16 @@ fn boolean_all<'a>(arena: &'a Bump, tests: Vec<(Expr<'a>, Expr<'a>, Layout<'a>)>
let mut expr = Expr::Bool(true); let mut expr = Expr::Bool(true);
for (lhs, rhs, layout) in tests.into_iter().rev() { for (lhs, rhs, layout) in tests.into_iter().rev() {
let test = specialize_equality(arena, lhs, rhs, layout); let test = specialize_equality(arena, lhs, rhs, layout.clone());
expr = Expr::CallByName(
Symbol::BOOL_AND, expr = Expr::CallByName {
arena.alloc([ name: Symbol::BOOL_AND,
layout,
args: arena.alloc([
(test, Layout::Builtin(Builtin::Bool)), (test, Layout::Builtin(Builtin::Bool)),
(expr, Layout::Builtin(Builtin::Bool)), (expr, Layout::Builtin(Builtin::Bool)),
]), ]),
); };
} }
expr expr

View file

@ -1,12 +1,13 @@
use crate::layout::{Builtin, Layout}; use crate::layout::{Builtin, Layout, LayoutCache};
use crate::pattern::{Ctor, Guard, RenderAs, TagId}; use crate::pattern::{Ctor, Guard, RenderAs, TagId};
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{default_hasher, MutMap, MutSet};
use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::{Content, ContentHash, FlatType, Subs, Variable}; use roc_types::subs::{Content, FlatType, Subs, Variable};
use std::collections::HashMap;
use std::hash::Hash; use std::hash::Hash;
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -29,8 +30,9 @@ pub struct Proc<'a> {
pub struct Procs<'a> { pub struct Procs<'a> {
pub user_defined: MutMap<Symbol, PartialProc<'a>>, pub user_defined: MutMap<Symbol, PartialProc<'a>>,
pub module_thunks: MutSet<Symbol>, pub module_thunks: MutSet<Symbol>,
anonymous: MutMap<Symbol, Option<Proc<'a>>>, runtime_errors: MutSet<Symbol>,
specializations: MutMap<ContentHash, (Symbol, Option<Proc<'a>>)>, specializations: MutMap<Symbol, MutMap<Layout<'a>, Proc<'a>>>,
pending_specializations: MutMap<Symbol, Layout<'a>>,
builtin: MutSet<Symbol>, builtin: MutSet<Symbol>,
} }
@ -57,6 +59,8 @@ impl<'a> Procs<'a> {
); );
} }
// TODO trim these down
#[allow(clippy::too_many_arguments)]
pub fn insert_anonymous( pub fn insert_anonymous(
&mut self, &mut self,
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
@ -65,13 +69,14 @@ impl<'a> Procs<'a> {
loc_args: std::vec::Vec<(Variable, Located<roc_can::pattern::Pattern>)>, loc_args: std::vec::Vec<(Variable, Located<roc_can::pattern::Pattern>)>,
loc_body: Located<roc_can::expr::Expr>, loc_body: Located<roc_can::expr::Expr>,
ret_var: Variable, ret_var: Variable,
) { layout_cache: &mut LayoutCache<'a>,
) -> Result<Layout<'a>, ()> {
let (arg_vars, arg_symbols, body) = patterns_to_when(env, loc_args, ret_var, loc_body); let (arg_vars, arg_symbols, body) = patterns_to_when(env, loc_args, ret_var, loc_body);
// an anonymous closure. These will always be specialized already // an anonymous closure. These will always be specialized already
// by the surrounding context // by the surrounding context
let opt_proc = specialize_proc_body( match specialize_proc_body(
env, env,
self, self,
annotation, annotation,
@ -81,19 +86,38 @@ impl<'a> Procs<'a> {
&arg_symbols, &arg_symbols,
annotation, annotation,
body.value, body.value,
) layout_cache,
.ok(); ) {
Ok(proc) => {
let layout = layout_cache
.from_var(env.arena, annotation, env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err));
self.anonymous.insert(symbol, opt_proc); self.insert_specialization(symbol, layout.clone(), proc);
Ok(layout)
}
Err(()) => {
self.runtime_errors.insert(symbol);
Err(())
}
}
} }
fn insert_specialization( fn insert_specialization(&mut self, symbol: Symbol, layout: Layout<'a>, proc: Proc<'a>) {
&mut self, let procs_by_layout = self
hash: ContentHash, .specializations
spec_name: Symbol, .entry(symbol)
proc: Option<Proc<'a>>, .or_insert_with(|| HashMap::with_capacity_and_hasher(1, default_hasher()));
) {
self.specializations.insert(hash, (spec_name, proc)); // If we already have an entry for this, it should be no different
// from what we're about to insert.
debug_assert!(
!procs_by_layout.contains_key(&layout) || procs_by_layout.get(&layout) == Some(&proc)
);
procs_by_layout.insert(layout, proc);
} }
fn get_user_defined(&self, symbol: Symbol) -> Option<&PartialProc<'a>> { fn get_user_defined(&self, symbol: Symbol) -> Option<&PartialProc<'a>> {
@ -101,10 +125,10 @@ impl<'a> Procs<'a> {
} }
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
let anonymous: usize = self.anonymous.len(); let runtime_errors: usize = self.runtime_errors.len();
let user_defined: usize = self.specializations.len(); let specializations: usize = self.specializations.len();
anonymous + user_defined runtime_errors + specializations
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
@ -115,22 +139,20 @@ impl<'a> Procs<'a> {
self.builtin.insert(symbol); self.builtin.insert(symbol);
} }
pub fn as_map(&self) -> MutMap<Symbol, Option<Proc<'a>>> { pub fn into_map(self) -> (MutMap<Symbol, MutMap<Layout<'a>, Proc<'a>>>, MutSet<Symbol>) {
let mut result = MutMap::default(); let mut specializations = self.specializations;
for (symbol, opt_proc) in self.specializations.values() {
result.insert(*symbol, opt_proc.clone());
}
for (symbol, proc) in self.anonymous.clone().into_iter() {
result.insert(symbol, proc);
}
for symbol in self.builtin.iter() { for symbol in self.builtin.iter() {
result.insert(*symbol, None); // Builtins should only ever be stored as empty maps.
debug_assert!(
!specializations.contains_key(&symbol)
|| specializations.get(&symbol).unwrap().is_empty()
);
specializations.insert(*symbol, MutMap::default());
} }
result (specializations, self.runtime_errors)
} }
} }
@ -177,8 +199,13 @@ pub enum Expr<'a> {
Store(&'a [(Symbol, Layout<'a>, Expr<'a>)], &'a Expr<'a>), Store(&'a [(Symbol, Layout<'a>, Expr<'a>)], &'a Expr<'a>),
// Functions // Functions
FunctionPointer(Symbol), FunctionPointer(Symbol, Layout<'a>),
CallByName(Symbol, &'a [(Expr<'a>, Layout<'a>)]), RuntimeErrorFunction(&'a str),
CallByName {
name: Symbol,
layout: Layout<'a>,
args: &'a [(Expr<'a>, Layout<'a>)],
},
CallByPointer(&'a Expr<'a>, &'a [Expr<'a>], Layout<'a>), CallByPointer(&'a Expr<'a>, &'a [Expr<'a>], Layout<'a>),
// Exactly two conditional branches, e.g. if/else // Exactly two conditional branches, e.g. if/else
@ -248,7 +275,9 @@ impl<'a> Expr<'a> {
can_expr: roc_can::expr::Expr, can_expr: roc_can::expr::Expr,
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
) -> Self { ) -> Self {
from_can(env, can_expr, procs, None) let mut layout_cache = LayoutCache::default();
from_can(env, can_expr, procs, &mut layout_cache)
} }
} }
@ -448,7 +477,7 @@ fn from_can<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
can_expr: roc_can::expr::Expr, can_expr: roc_can::expr::Expr,
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
name: Option<Symbol>, layout_cache: &mut LayoutCache<'a>,
) -> Expr<'a> { ) -> Expr<'a> {
use roc_can::expr::Expr::*; use roc_can::expr::Expr::*;
@ -467,123 +496,51 @@ fn from_can<'a>(
let ret_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. // This is a top-level declaration, which will code gen to a 0-arity thunk.
call_by_name(env, procs, fn_var, ret_var, symbol, std::vec::Vec::new()) call_by_name(
env,
procs,
fn_var,
ret_var,
symbol,
std::vec::Vec::new(),
layout_cache,
)
} else { } else {
Expr::Load(symbol) Expr::Load(symbol)
} }
} }
LetRec(defs, ret_expr, _, _) => from_can_defs(env, defs, *ret_expr, procs), LetRec(defs, ret_expr, _, _) => from_can_defs(env, defs, *ret_expr, layout_cache, procs),
LetNonRec(def, ret_expr, _, _) => from_can_defs(env, vec![*def], *ret_expr, procs), LetNonRec(def, ret_expr, _, _) => {
from_can_defs(env, vec![*def], *ret_expr, layout_cache, procs)
}
Closure(ann, original_name, _, loc_args, boxed_body) => { Closure(ann, name, _, loc_args, boxed_body) => {
let (loc_body, ret_var) = *boxed_body; let (loc_body, ret_var) = *boxed_body;
let symbol = match name {
Some(symbol) => {
procs.insert_named(env, symbol, ann, loc_args, loc_body, ret_var);
symbol match procs.insert_anonymous(env, name, ann, loc_args, loc_body, ret_var, layout_cache)
{
Ok(layout) => Expr::FunctionPointer(name, layout),
Err(()) => {
// TODO make this message better
Expr::RuntimeErrorFunction("This function threw a runtime error.")
} }
None => {
procs.insert_anonymous(env, original_name, ann, loc_args, loc_body, ret_var);
original_name
} }
};
Expr::FunctionPointer(symbol)
} }
Call(boxed, loc_args, _) => { Call(boxed, loc_args, _) => {
use IntOrFloat::*;
let (fn_var, loc_expr, ret_var) = *boxed; let (fn_var, loc_expr, ret_var) = *boxed;
let specialize_builtin_functions = { match from_can(env, loc_expr.value, procs, layout_cache) {
|env: &mut Env<'a, '_>, symbol: Symbol| {
if !symbol.module_id().is_builtin() {
// return unchanged
symbol
} else {
match symbol {
Symbol::NUM_ADD => match num_to_int_or_float(env.subs, ret_var) {
FloatType => Symbol::FLOAT_ADD,
IntType => Symbol::INT_ADD,
},
Symbol::NUM_SUB => match num_to_int_or_float(env.subs, ret_var) {
FloatType => Symbol::FLOAT_SUB,
IntType => Symbol::INT_SUB,
},
Symbol::NUM_LTE => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_LTE,
IntType => Symbol::INT_LTE,
},
Symbol::NUM_LT => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_LT,
IntType => Symbol::INT_LT,
},
Symbol::NUM_GTE => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_GTE,
IntType => Symbol::INT_GTE,
},
Symbol::NUM_GT => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_GT,
IntType => Symbol::INT_GT,
},
// TODO make this work for more than just int/float
Symbol::BOOL_EQ => {
match Layout::from_var(
env.arena,
loc_args[0].0,
env.subs,
env.pointer_size,
) {
Ok(Layout::Builtin(builtin)) => match builtin {
Builtin::Int64 => Symbol::INT_EQ_I64,
Builtin::Float64 => Symbol::FLOAT_EQ,
Builtin::Bool => Symbol::INT_EQ_I1,
Builtin::Byte => Symbol::INT_EQ_I8,
_ => panic!("Equality not implemented for {:?}", builtin),
},
Ok(complex) => panic!(
"TODO support equality on complex layouts like {:?}",
complex
),
Err(()) => panic!("Invalid layout"),
}
}
Symbol::BOOL_NEQ => {
match Layout::from_var(
env.arena,
loc_args[0].0,
env.subs,
env.pointer_size,
) {
Ok(Layout::Builtin(builtin)) => match builtin {
Builtin::Int64 => Symbol::INT_NEQ_I64,
Builtin::Bool => Symbol::INT_NEQ_I1,
Builtin::Byte => Symbol::INT_NEQ_I8,
_ => {
panic!("Not-Equality not implemented for {:?}", builtin)
}
},
Ok(complex) => panic!(
"TODO support equality on complex layouts like {:?}",
complex
),
Err(()) => panic!("Invalid layout"),
}
}
_ => symbol,
}
}
}
};
match from_can(env, loc_expr.value, procs, None) {
Expr::Load(proc_name) => { Expr::Load(proc_name) => {
// Some functions can potentially mutate in-place. // Some functions can potentially mutate in-place.
// If we have one of those, switch to the in-place version if appropriate. // If we have one of those, switch to the in-place version if appropriate.
match specialize_builtin_functions(env, proc_name) { match specialize_builtin_functions(
env,
proc_name,
loc_args.as_slice(),
ret_var,
layout_cache,
) {
Symbol::LIST_SET => { Symbol::LIST_SET => {
let subs = &env.subs; let subs = &env.subs;
// The first arg is the one with the List in it. // The first arg is the one with the List in it.
@ -610,9 +567,25 @@ fn from_can<'a>(
Symbol::LIST_SET Symbol::LIST_SET
}; };
call_by_name(env, procs, fn_var, ret_var, new_name, loc_args) call_by_name(
env,
procs,
fn_var,
ret_var,
new_name,
loc_args,
layout_cache,
)
} }
_ => call_by_name(env, procs, fn_var, ret_var, proc_name, loc_args), _ => call_by_name(
env,
procs,
fn_var,
ret_var,
proc_name,
loc_args,
layout_cache,
),
} }
} }
specialized_proc_symbol => call_by_name( specialized_proc_symbol => call_by_name(
@ -622,6 +595,7 @@ fn from_can<'a>(
ret_var, ret_var,
specialized_proc_symbol, specialized_proc_symbol,
loc_args, loc_args,
layout_cache,
), ),
} }
} }
@ -640,10 +614,11 @@ fn from_can<'a>(
let mut args = Vec::with_capacity_in(loc_args.len(), env.arena); let mut args = Vec::with_capacity_in(loc_args.len(), env.arena);
for (_, loc_arg) in loc_args { for (_, loc_arg) in loc_args {
args.push(from_can(env, loc_arg.value, procs, None)); args.push(from_can(env, loc_arg.value, procs, layout_cache));
} }
let layout = Layout::from_var(env.arena, fn_var, env.subs, env.pointer_size) let layout = layout_cache
.from_var(env.arena, fn_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| { .unwrap_or_else(|err| {
panic!("TODO turn fn_var into a RuntimeError {:?}", err) panic!("TODO turn fn_var into a RuntimeError {:?}", err)
}); });
@ -658,7 +633,16 @@ fn from_can<'a>(
region, region,
loc_cond, loc_cond,
branches, branches,
} => from_can_when(env, cond_var, expr_var, region, *loc_cond, branches, procs), } => from_can_when(
env,
cond_var,
expr_var,
region,
*loc_cond,
branches,
layout_cache,
procs,
),
If { If {
cond_var, cond_var,
@ -666,16 +650,18 @@ fn from_can<'a>(
branches, branches,
final_else, final_else,
} => { } => {
let mut expr = from_can(env, final_else.value, procs, None); let mut expr = from_can(env, final_else.value, procs, layout_cache);
let ret_layout = Layout::from_var(env.arena, branch_var, env.subs, env.pointer_size) let ret_layout = layout_cache
.from_var(env.arena, branch_var, env.subs, env.pointer_size)
.expect("invalid ret_layout"); .expect("invalid ret_layout");
let cond_layout = Layout::from_var(env.arena, cond_var, env.subs, env.pointer_size) let cond_layout = layout_cache
.from_var(env.arena, cond_var, env.subs, env.pointer_size)
.expect("invalid cond_layout"); .expect("invalid cond_layout");
for (loc_cond, loc_then) in branches.into_iter().rev() { for (loc_cond, loc_then) in branches.into_iter().rev() {
let cond = from_can(env, loc_cond.value, procs, None); let cond = from_can(env, loc_cond.value, procs, layout_cache);
let then = from_can(env, loc_then.value, procs, None); let then = from_can(env, loc_then.value, procs, layout_cache);
let branch_symbol = env.unique_symbol(); let branch_symbol = env.unique_symbol();
@ -716,7 +702,7 @@ fn from_can<'a>(
for (label, layout) in btree { for (label, layout) in btree {
let field = fields.remove(&label).unwrap(); let field = fields.remove(&label).unwrap();
let expr = from_can(env, field.loc_expr.value, procs, None); let expr = from_can(env, field.loc_expr.value, procs, layout_cache);
field_tuples.push((expr, layout)); field_tuples.push((expr, layout));
} }
@ -757,7 +743,7 @@ fn from_can<'a>(
Unwrapped(field_layouts) => { Unwrapped(field_layouts) => {
let field_exprs = args let field_exprs = args
.into_iter() .into_iter()
.map(|(_, arg)| from_can(env, arg.value, procs, None)); .map(|(_, arg)| from_can(env, arg.value, procs, layout_cache));
let mut field_tuples = Vec::with_capacity_in(field_layouts.len(), arena); let mut field_tuples = Vec::with_capacity_in(field_layouts.len(), arena);
@ -779,7 +765,7 @@ fn from_can<'a>(
let it = std::iter::once(Expr::Int(tag_id as i64)).chain( let it = std::iter::once(Expr::Int(tag_id as i64)).chain(
args.into_iter() args.into_iter()
.map(|(_, arg)| from_can(env, arg.value, procs, None)), .map(|(_, arg)| from_can(env, arg.value, procs, layout_cache)),
); );
for (arg_layout, arg_expr) in argument_layouts.iter().zip(it) { for (arg_layout, arg_expr) in argument_layouts.iter().zip(it) {
@ -832,7 +818,7 @@ fn from_can<'a>(
} }
} }
let record = arena.alloc(from_can(env, loc_expr.value, procs, None)); let record = arena.alloc(from_can(env, loc_expr.value, procs, layout_cache));
Expr::AccessAtIndex { Expr::AccessAtIndex {
index: index.expect("field not in its own type") as u64, index: index.expect("field not in its own type") as u64,
@ -853,8 +839,8 @@ fn from_can<'a>(
// We have to special-case the empty list, because trying to // We have to special-case the empty list, because trying to
// compute a layout for an unbound var won't work. // compute a layout for an unbound var won't work.
Content::FlexVar(_) => Layout::Builtin(Builtin::EmptyList), Content::FlexVar(_) => Layout::Builtin(Builtin::EmptyList),
content => match Layout::from_content(arena, content, env.subs, env.pointer_size) { _ => match layout_cache.from_var(arena, elem_var, env.subs, env.pointer_size) {
Ok(layout) => layout, Ok(layout) => layout.clone(),
Err(()) => { Err(()) => {
panic!("TODO gracefully handle List with invalid element layout"); panic!("TODO gracefully handle List with invalid element layout");
} }
@ -864,7 +850,7 @@ fn from_can<'a>(
let mut elems = Vec::with_capacity_in(loc_elems.len(), arena); let mut elems = Vec::with_capacity_in(loc_elems.len(), arena);
for loc_elem in loc_elems { for loc_elem in loc_elems {
elems.push(from_can(env, loc_elem.value, procs, None)); elems.push(from_can(env, loc_elem.value, procs, layout_cache));
} }
Expr::Array { Expr::Array {
@ -1022,6 +1008,7 @@ fn from_can_defs<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
defs: std::vec::Vec<roc_can::def::Def>, defs: std::vec::Vec<roc_can::def::Def>,
ret_expr: Located<roc_can::expr::Expr>, ret_expr: Located<roc_can::expr::Expr>,
layout_cache: &mut LayoutCache<'a>,
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
) -> Expr<'a> { ) -> Expr<'a> {
use roc_can::expr::Expr::*; use roc_can::expr::Expr::*;
@ -1029,6 +1016,7 @@ fn from_can_defs<'a>(
let arena = env.arena; let arena = env.arena;
let mut stored = Vec::with_capacity_in(defs.len(), arena); let mut stored = Vec::with_capacity_in(defs.len(), arena);
for def in defs { for def in defs {
let loc_pattern = def.loc_pattern; let loc_pattern = def.loc_pattern;
let loc_expr = def.loc_expr; let loc_expr = def.loc_expr;
@ -1047,18 +1035,29 @@ fn from_can_defs<'a>(
// //
if let Identifier(symbol) = &loc_pattern.value { if let Identifier(symbol) = &loc_pattern.value {
if let Closure(_, _, _, _, _) = &loc_expr.value { if let Closure(_, _, _, _, _) = &loc_expr.value {
// Now that we know for sure it's a closure, get an owned
// version of these variant args so we can use them properly.
match loc_expr.value {
Closure(ann, _, _, loc_args, boxed_body) => {
// Extract Procs, but discard the resulting Expr::Load. // Extract Procs, but discard the resulting Expr::Load.
// That Load looks up the pointer, which we won't use here! // That Load looks up the pointer, which we won't use here!
from_can(env, loc_expr.value, procs, Some(*symbol));
let (loc_body, ret_var) = *boxed_body;
procs.insert_named(env, *symbol, ann, loc_args, loc_body, ret_var);
continue; continue;
} }
_ => unreachable!(),
}
}
} }
// If it wasn't specifically an Identifier & Closure, proceed as normal. // If it wasn't specifically an Identifier & Closure, proceed as normal.
let mono_pattern = from_can_pattern(env, &loc_pattern.value); let mono_pattern = from_can_pattern(env, &loc_pattern.value);
let layout = Layout::from_var(env.arena, def.expr_var, env.subs, env.pointer_size) let layout = layout_cache
.from_var(env.arena, def.expr_var, env.subs, env.pointer_size)
.expect("invalid layout"); .expect("invalid layout");
match &mono_pattern { match &mono_pattern {
@ -1066,7 +1065,7 @@ fn from_can_defs<'a>(
stored.push(( stored.push((
*symbol, *symbol,
layout.clone(), layout.clone(),
from_can(env, loc_expr.value, procs, None), from_can(env, loc_expr.value, procs, layout_cache),
)); ));
} }
_ => { _ => {
@ -1093,7 +1092,7 @@ fn from_can_defs<'a>(
stored.push(( stored.push((
symbol, symbol,
layout.clone(), layout.clone(),
from_can(env, loc_expr.value, procs, None), from_can(env, loc_expr.value, procs, layout_cache),
)); ));
match store_pattern(env, &mono_pattern, symbol, layout, &mut stored) { match store_pattern(env, &mono_pattern, symbol, layout, &mut stored) {
@ -1108,7 +1107,7 @@ fn from_can_defs<'a>(
} }
// At this point, it's safe to assume we aren't assigning a Closure to a def. // At this point, it's safe to assume we aren't assigning a Closure to a def.
// Extract Procs from the def body and the ret expression, and return the result! // Extract Procs from the def body and the ret expression, and return the result!
let ret = from_can(env, ret_expr.value, procs, None); let ret = from_can(env, ret_expr.value, procs, layout_cache);
if stored.is_empty() { if stored.is_empty() {
ret ret
@ -1117,6 +1116,8 @@ fn from_can_defs<'a>(
} }
} }
// TODO trim these down
#[allow(clippy::too_many_arguments)]
fn from_can_when<'a>( fn from_can_when<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
cond_var: Variable, cond_var: Variable,
@ -1124,6 +1125,7 @@ fn from_can_when<'a>(
region: Region, region: Region,
loc_cond: Located<roc_can::expr::Expr>, loc_cond: Located<roc_can::expr::Expr>,
mut branches: std::vec::Vec<roc_can::expr::WhenBranch>, mut branches: std::vec::Vec<roc_can::expr::WhenBranch>,
layout_cache: &mut LayoutCache<'a>,
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
) -> Expr<'a> { ) -> Expr<'a> {
if branches.is_empty() { if branches.is_empty() {
@ -1168,32 +1170,34 @@ fn from_can_when<'a>(
} }
} }
let cond_layout = Layout::from_var(env.arena, cond_var, env.subs, env.pointer_size) let cond_layout = layout_cache
.from_var(env.arena, cond_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err)); .unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err));
let cond_symbol = env.unique_symbol(); let cond_symbol = env.unique_symbol();
let cond = from_can(env, loc_cond.value, procs, None); let cond = from_can(env, loc_cond.value, procs, layout_cache);
stored.push((cond_symbol, cond_layout.clone(), cond)); stored.push((cond_symbol, cond_layout.clone(), cond));
// NOTE this will still store shadowed names. // NOTE this will still store shadowed names.
// that's fine: the branch throws a runtime error anyway // that's fine: the branch throws a runtime error anyway
let ret = match store_pattern(env, &mono_pattern, cond_symbol, cond_layout, &mut stored) { let ret = match store_pattern(env, &mono_pattern, cond_symbol, cond_layout, &mut stored) {
Ok(_) => from_can(env, first.value.value, procs, None), Ok(_) => from_can(env, first.value.value, procs, layout_cache),
Err(message) => Expr::RuntimeError(env.arena.alloc(message)), Err(message) => Expr::RuntimeError(env.arena.alloc(message)),
}; };
Expr::Store(stored.into_bump_slice(), arena.alloc(ret)) Expr::Store(stored.into_bump_slice(), arena.alloc(ret))
} else { } else {
let cond_layout = Layout::from_var(env.arena, cond_var, env.subs, env.pointer_size) let cond_layout = layout_cache
.from_var(env.arena, cond_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err)); .unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err));
let cond = from_can(env, loc_cond.value, procs, None); let cond = from_can(env, loc_cond.value, procs, layout_cache);
let cond_symbol = env.unique_symbol(); let cond_symbol = env.unique_symbol();
let mut loc_branches = std::vec::Vec::new(); let mut loc_branches = std::vec::Vec::new();
let mut opt_branches = std::vec::Vec::new(); let mut opt_branches = std::vec::Vec::new();
for when_branch in branches { for when_branch in branches {
let mono_expr = from_can(env, when_branch.value.value, procs, None); let mono_expr = from_can(env, when_branch.value.value, procs, layout_cache);
let exhaustive_guard = if when_branch.guard.is_some() { let exhaustive_guard = if when_branch.guard.is_some() {
Guard::HasGuard Guard::HasGuard
@ -1226,7 +1230,7 @@ fn from_can_when<'a>(
// //
// otherwise, we modify the branch's expression to include the stores // otherwise, we modify the branch's expression to include the stores
if let Some(loc_guard) = when_branch.guard.clone() { if let Some(loc_guard) = when_branch.guard.clone() {
let expr = from_can(env, loc_guard.value, procs, None); let expr = from_can(env, loc_guard.value, procs, layout_cache);
( (
crate::decision_tree::Guard::Guard { crate::decision_tree::Guard::Guard {
stores: stores.into_bump_slice(), stores: stores.into_bump_slice(),
@ -1306,7 +1310,8 @@ fn from_can_when<'a>(
} }
} }
let ret_layout = Layout::from_var(env.arena, expr_var, env.subs, env.pointer_size) let ret_layout = layout_cache
.from_var(env.arena, expr_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err)); .unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err));
let branching = crate::decision_tree::optimize_when( let branching = crate::decision_tree::optimize_when(
@ -1330,6 +1335,7 @@ fn call_by_name<'a>(
ret_var: Variable, ret_var: Variable,
proc_name: Symbol, proc_name: Symbol,
loc_args: std::vec::Vec<(Variable, Located<roc_can::expr::Expr>)>, loc_args: std::vec::Vec<(Variable, Located<roc_can::expr::Expr>)>,
layout_cache: &mut LayoutCache<'a>,
) -> Expr<'a> { ) -> Expr<'a> {
// create specialized procedure to call // create specialized procedure to call
@ -1339,34 +1345,33 @@ fn call_by_name<'a>(
// get a borrow checker error about trying to borrow `procs` as mutable // get a borrow checker error about trying to borrow `procs` as mutable
// while there is still an active immutable borrow. // while there is still an active immutable borrow.
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
let opt_specialize_body: Option<( let opt_specialize_body: Option<(Variable, roc_can::expr::Expr, Vec<'a, Symbol>)>;
ContentHash,
Variable,
roc_can::expr::Expr,
Vec<'a, Symbol>,
)>;
let specialized_proc_name = match procs.get_user_defined(proc_name) { match layout_cache.from_var(env.arena, fn_var, env.subs, env.pointer_size) {
Ok(layout) => {
match procs.get_user_defined(proc_name) {
Some(partial_proc) => { Some(partial_proc) => {
let content_hash = ContentHash::from_var(fn_var, env.subs); match procs
.specializations
match procs.specializations.get(&content_hash) { .get(&proc_name)
Some(specialization) => { .and_then(|procs_by_layout| procs_by_layout.get(&layout))
{
Some(_) => {
// a specialization with this layout already exists.
opt_specialize_body = None; opt_specialize_body = None;
// a specialization with this type hash already exists, so use its symbol
specialization.0
} }
None => { None => {
if procs.pending_specializations.get(&proc_name) == Some(&layout) {
// If we're already in the process of specializing this, don't
// try to specialize it further; otherwise, we'll loop forever.
opt_specialize_body = None;
} else {
opt_specialize_body = Some(( opt_specialize_body = Some((
content_hash,
partial_proc.annotation, partial_proc.annotation,
partial_proc.body.clone(), partial_proc.body.clone(),
partial_proc.patterns.clone(), partial_proc.patterns.clone(),
)); ));
}
// generate a symbol for this specialization
env.unique_symbol()
} }
} }
} }
@ -1375,43 +1380,61 @@ fn call_by_name<'a>(
// This happens for built-in symbols (they are never defined as a Closure) // This happens for built-in symbols (they are never defined as a Closure)
procs.insert_builtin(proc_name); procs.insert_builtin(proc_name);
proc_name
} }
}; };
if let Some((content_hash, annotation, body, loc_patterns)) = opt_specialize_body { if let Some((annotation, body, loc_patterns)) = opt_specialize_body {
// register proc, so specialization doesn't loop infinitely // register proc, so specialization doesn't loop infinitely
procs.insert_specialization(content_hash, specialized_proc_name, None); procs
.pending_specializations
.insert(proc_name, layout.clone());
let arg_vars = loc_args.iter().map(|v| v.0).collect::<std::vec::Vec<_>>(); let arg_vars = loc_args.iter().map(|v| v.0).collect::<std::vec::Vec<_>>();
let proc = specialize_proc_body( match specialize_proc_body(
env, env,
procs, procs,
fn_var, fn_var,
ret_var, ret_var,
specialized_proc_name, proc_name,
&arg_vars, &arg_vars,
&loc_patterns, &loc_patterns,
annotation, annotation,
body, body,
) layout_cache,
.ok(); ) {
Ok(proc) => {
procs.insert_specialization(content_hash, specialized_proc_name, proc); procs.insert_specialization(proc_name, layout.clone(), proc);
}
Err(()) => {
procs.runtime_errors.insert(proc_name);
}
}
} }
// generate actual call // generate actual call
let mut args = Vec::with_capacity_in(loc_args.len(), env.arena); let mut args = Vec::with_capacity_in(loc_args.len(), env.arena);
for (var, loc_arg) in loc_args { for (var, loc_arg) in loc_args {
let layout = Layout::from_var(&env.arena, var, &env.subs, env.pointer_size) let layout = layout_cache
.from_var(&env.arena, var, &env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO gracefully handle bad layout: {:?}", err)); .unwrap_or_else(|err| panic!("TODO gracefully handle bad layout: {:?}", err));
args.push((from_can(env, loc_arg.value, procs, None), layout)); args.push((from_can(env, loc_arg.value, procs, layout_cache), layout));
} }
Expr::CallByName(specialized_proc_name, args.into_bump_slice()) Expr::CallByName {
name: proc_name,
layout,
args: args.into_bump_slice(),
}
}
Err(()) => {
// This function code gens to a runtime error,
// so attempting to call it will immediately crash.
Expr::RuntimeError("")
}
}
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@ -1425,23 +1448,26 @@ fn specialize_proc_body<'a>(
pattern_symbols: &[Symbol], pattern_symbols: &[Symbol],
annotation: Variable, annotation: Variable,
body: roc_can::expr::Expr, body: roc_can::expr::Expr,
layout_cache: &mut LayoutCache<'a>,
) -> Result<Proc<'a>, ()> { ) -> Result<Proc<'a>, ()> {
// unify the called function with the specialized signature, then specialize the function body // unify the called function with the specialized signature, then specialize the function body
let snapshot = env.subs.snapshot(); let snapshot = env.subs.snapshot();
let unified = roc_unify::unify::unify(env.subs, annotation, fn_var); let unified = roc_unify::unify::unify(env.subs, annotation, fn_var);
debug_assert!(matches!(unified, roc_unify::unify::Unified::Success(_))); debug_assert!(matches!(unified, roc_unify::unify::Unified::Success(_)));
let specialized_body = from_can(env, body, procs, None); let specialized_body = from_can(env, body, procs, layout_cache);
// reset subs, so we don't get type errors when specializing for a different signature // reset subs, so we don't get type errors when specializing for a different signature
env.subs.rollback_to(snapshot); env.subs.rollback_to(snapshot);
let mut proc_args = Vec::with_capacity_in(loc_args.len(), &env.arena); let mut proc_args = Vec::with_capacity_in(loc_args.len(), &env.arena);
for (arg_var, arg_name) in loc_args.iter().zip(pattern_symbols.iter()) { for (arg_var, arg_name) in loc_args.iter().zip(pattern_symbols.iter()) {
let layout = Layout::from_var(&env.arena, *arg_var, env.subs, env.pointer_size)?; let layout = layout_cache.from_var(&env.arena, *arg_var, env.subs, env.pointer_size)?;
proc_args.push((layout, *arg_name)); proc_args.push((layout, *arg_name));
} }
let ret_layout = Layout::from_var(&env.arena, ret_var, env.subs, env.pointer_size) let ret_layout = layout_cache
.from_var(&env.arena, ret_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err)); .unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err));
let proc = Proc { let proc = Proc {
@ -1752,7 +1778,7 @@ pub fn specialize_equality<'a>(
rhs: Expr<'a>, rhs: Expr<'a>,
layout: Layout<'a>, layout: Layout<'a>,
) -> Expr<'a> { ) -> Expr<'a> {
let symbol = match &layout { let name = match &layout {
Layout::Builtin(builtin) => match builtin { Layout::Builtin(builtin) => match builtin {
Builtin::Int64 => Symbol::INT_EQ_I64, Builtin::Int64 => Symbol::INT_EQ_I64,
Builtin::Float64 => Symbol::FLOAT_EQ, Builtin::Float64 => Symbol::FLOAT_EQ,
@ -1763,8 +1789,84 @@ pub fn specialize_equality<'a>(
other => todo!("Cannot yet compare for equality {:?}", other), other => todo!("Cannot yet compare for equality {:?}", other),
}; };
Expr::CallByName( Expr::CallByName {
symbol, name,
arena.alloc([(lhs, layout.clone()), (rhs, layout.clone())]), layout: layout.clone(),
) args: arena.alloc([(lhs, layout.clone()), (rhs, layout)]),
}
}
fn specialize_builtin_functions<'a>(
env: &mut Env<'a, '_>,
symbol: Symbol,
loc_args: &[(Variable, Located<roc_can::expr::Expr>)],
ret_var: Variable,
layout_cache: &mut LayoutCache<'a>,
) -> Symbol {
use IntOrFloat::*;
if !symbol.is_builtin() {
// return unchanged
symbol
} else {
match symbol {
Symbol::NUM_ADD => match num_to_int_or_float(env.subs, ret_var) {
FloatType => Symbol::FLOAT_ADD,
IntType => Symbol::INT_ADD,
},
Symbol::NUM_SUB => match num_to_int_or_float(env.subs, ret_var) {
FloatType => Symbol::FLOAT_SUB,
IntType => Symbol::INT_SUB,
},
Symbol::NUM_LTE => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_LTE,
IntType => Symbol::INT_LTE,
},
Symbol::NUM_LT => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_LT,
IntType => Symbol::INT_LT,
},
Symbol::NUM_GTE => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_GTE,
IntType => Symbol::INT_GTE,
},
Symbol::NUM_GT => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_GT,
IntType => Symbol::INT_GT,
},
// TODO make this work for more than just int/float
Symbol::BOOL_EQ => {
match layout_cache.from_var(env.arena, loc_args[0].0, env.subs, env.pointer_size) {
Ok(Layout::Builtin(builtin)) => match builtin {
Builtin::Int64 => Symbol::INT_EQ_I64,
Builtin::Float64 => Symbol::FLOAT_EQ,
Builtin::Bool => Symbol::INT_EQ_I1,
Builtin::Byte => Symbol::INT_EQ_I8,
_ => panic!("Equality not implemented for {:?}", builtin),
},
Ok(complex) => panic!(
"TODO support equality on complex layouts like {:?}",
complex
),
Err(()) => panic!("Invalid layout"),
}
}
Symbol::BOOL_NEQ => {
match layout_cache.from_var(env.arena, loc_args[0].0, env.subs, env.pointer_size) {
Ok(Layout::Builtin(builtin)) => match builtin {
Builtin::Int64 => Symbol::INT_NEQ_I64,
Builtin::Bool => Symbol::INT_NEQ_I1,
Builtin::Byte => Symbol::INT_NEQ_I8,
_ => panic!("Not-Equality not implemented for {:?}", builtin),
},
Ok(complex) => panic!(
"TODO support equality on complex layouts like {:?}",
complex
),
Err(()) => panic!("Invalid layout"),
}
}
_ => symbol,
}
}
} }

View file

@ -36,21 +36,7 @@ pub enum Builtin<'a> {
} }
impl<'a> Layout<'a> { impl<'a> Layout<'a> {
/// Returns Err(()) if given an error, or Ok(Layout) if given a non-erroneous Structure. pub fn new(
/// Panics if given a FlexVar or RigidVar, since those should have been
/// monomorphized away already!
pub fn from_var(
arena: &'a Bump,
var: Variable,
subs: &Subs,
pointer_size: u32,
) -> Result<Self, ()> {
let content = subs.get_without_compacting(var).content;
Self::from_content(arena, content, subs, pointer_size)
}
pub fn from_content(
arena: &'a Bump, arena: &'a Bump,
content: Content, content: Content,
subs: &Subs, subs: &Subs,
@ -61,7 +47,7 @@ impl<'a> Layout<'a> {
match content { match content {
var @ FlexVar(_) | var @ RigidVar(_) => { var @ FlexVar(_) | var @ RigidVar(_) => {
panic!( panic!(
"Layout::from_content encountered an unresolved {:?} - subs was {:?}", "Layout::new encountered an unresolved {:?} - subs was {:?}",
var, subs var, subs
); );
} }
@ -75,7 +61,7 @@ impl<'a> Layout<'a> {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Float64)) Ok(Layout::Builtin(Builtin::Float64))
} }
Alias(_, _, var) => Self::from_content( Alias(_, _, var) => Self::new(
arena, arena,
subs.get_without_compacting(var).content, subs.get_without_compacting(var).content,
subs, subs,
@ -85,6 +71,20 @@ impl<'a> Layout<'a> {
} }
} }
/// Returns Err(()) if given an error, or Ok(Layout) if given a non-erroneous Structure.
/// Panics if given a FlexVar or RigidVar, since those should have been
/// monomorphized away already!
fn from_var(
arena: &'a Bump,
var: Variable,
subs: &Subs,
pointer_size: u32,
) -> Result<Self, ()> {
let content = subs.get_without_compacting(var).content;
Self::new(arena, content, subs, pointer_size)
}
pub fn safe_to_memcpy(&self) -> bool { pub fn safe_to_memcpy(&self) -> bool {
use Layout::*; use Layout::*;
@ -137,6 +137,37 @@ impl<'a> Layout<'a> {
} }
} }
/// Avoid recomputing Layout from Variable multiple times.
#[derive(Default)]
pub struct LayoutCache<'a> {
layouts: MutMap<Variable, Result<Layout<'a>, ()>>,
}
impl<'a> LayoutCache<'a> {
/// Returns Err(()) if given an error, or Ok(Layout) if given a non-erroneous Structure.
/// Panics if given a FlexVar or RigidVar, since those should have been
/// monomorphized away already!
pub fn from_var(
&mut self,
arena: &'a Bump,
var: Variable,
subs: &Subs,
pointer_size: u32,
) -> Result<Layout<'a>, ()> {
// Store things according to the root Variable, to avoid duplicate work.
let var = subs.get_root_key_without_compacting(var);
self.layouts
.entry(var)
.or_insert_with(|| {
let content = subs.get_without_compacting(var).content;
Layout::new(arena, content, subs, pointer_size)
})
.clone()
}
}
impl<'a> Builtin<'a> { impl<'a> Builtin<'a> {
const I64_SIZE: u32 = std::mem::size_of::<i64>() as u32; const I64_SIZE: u32 = std::mem::size_of::<i64>() as u32;
const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32; const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32;
@ -216,8 +247,7 @@ fn layout_from_flat_type<'a>(
match subs.get_without_compacting(args[0]).content { match subs.get_without_compacting(args[0]).content {
FlexVar(_) | RigidVar(_) => Ok(Layout::Builtin(Builtin::EmptyList)), FlexVar(_) | RigidVar(_) => Ok(Layout::Builtin(Builtin::EmptyList)),
content => { content => {
let elem_layout = let elem_layout = Layout::new(arena, content, subs, pointer_size)?;
Layout::from_content(arena, content, subs, pointer_size)?;
Ok(Layout::Builtin(Builtin::List(arena.alloc(elem_layout)))) Ok(Layout::Builtin(Builtin::List(arena.alloc(elem_layout))))
} }
@ -246,16 +276,11 @@ fn layout_from_flat_type<'a>(
for arg_var in args { for arg_var in args {
let arg_content = subs.get_without_compacting(arg_var).content; let arg_content = subs.get_without_compacting(arg_var).content;
fn_args.push(Layout::from_content( fn_args.push(Layout::new(arena, arg_content, subs, pointer_size)?);
arena,
arg_content,
subs,
pointer_size,
)?);
} }
let ret_content = subs.get_without_compacting(ret_var).content; let ret_content = subs.get_without_compacting(ret_var).content;
let ret = Layout::from_content(arena, ret_content, subs, pointer_size)?; let ret = Layout::new(arena, ret_content, subs, pointer_size)?;
Ok(Layout::FunctionPointer( Ok(Layout::FunctionPointer(
fn_args.into_bump_slice(), fn_args.into_bump_slice(),
@ -273,8 +298,7 @@ fn layout_from_flat_type<'a>(
for (_, field_var) in btree { for (_, field_var) in btree {
let field_content = subs.get_without_compacting(field_var).content; let field_content = subs.get_without_compacting(field_var).content;
let field_layout = let field_layout = match Layout::new(arena, field_content, subs, pointer_size) {
match Layout::from_content(arena, field_content, subs, pointer_size) {
Ok(layout) => layout, Ok(layout) => layout,
Err(()) => { Err(()) => {
// Invalid field! // Invalid field!

View file

@ -10,6 +10,7 @@
// and encouraging shortcuts here creates bad incentives. I would rather temporarily // and encouraging shortcuts here creates bad incentives. I would rather temporarily
// re-enable this when working on performance optimizations than have it block PRs. // re-enable this when working on performance optimizations than have it block PRs.
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant)]
pub mod expr; pub mod expr;
pub mod layout; pub mod layout;

View file

@ -67,7 +67,7 @@ mod test_mono {
// Put this module's ident_ids back in the interns // Put this module's ident_ids back in the interns
interns.all_ident_ids.insert(home, ident_ids); interns.all_ident_ids.insert(home, ident_ids);
assert_eq!(mono_expr, get_expected(interns)); assert_eq!(get_expected(interns), mono_expr);
} }
#[test] #[test]
@ -84,13 +84,20 @@ mod test_mono {
fn float_addition() { fn float_addition() {
compiles_to( compiles_to(
"3.0 + 4", "3.0 + 4",
CallByName( CallByName {
Symbol::FLOAT_ADD, name: Symbol::FLOAT_ADD,
layout: Layout::FunctionPointer(
&[ &[
Layout::Builtin(Builtin::Float64),
Layout::Builtin(Builtin::Float64),
],
&Layout::Builtin(Builtin::Float64),
),
args: &[
(Float(3.0), Layout::Builtin(Builtin::Float64)), (Float(3.0), Layout::Builtin(Builtin::Float64)),
(Float(4.0), Layout::Builtin(Builtin::Float64)), (Float(4.0), Layout::Builtin(Builtin::Float64)),
], ],
), },
); );
} }
@ -98,13 +105,20 @@ mod test_mono {
fn int_addition() { fn int_addition() {
compiles_to( compiles_to(
"0xDEADBEEF + 4", "0xDEADBEEF + 4",
CallByName( CallByName {
Symbol::INT_ADD, name: Symbol::INT_ADD,
layout: Layout::FunctionPointer(
&[ &[
Layout::Builtin(Builtin::Int64),
Layout::Builtin(Builtin::Int64),
],
&Layout::Builtin(Builtin::Int64),
),
args: &[
(Int(3735928559), Layout::Builtin(Builtin::Int64)), (Int(3735928559), Layout::Builtin(Builtin::Int64)),
(Int(4), Layout::Builtin(Builtin::Int64)), (Int(4), Layout::Builtin(Builtin::Int64)),
], ],
), },
); );
} }
@ -113,13 +127,20 @@ mod test_mono {
// Default to Int for `Num *` // Default to Int for `Num *`
compiles_to( compiles_to(
"3 + 5", "3 + 5",
CallByName( CallByName {
Symbol::INT_ADD, name: Symbol::INT_ADD,
layout: Layout::FunctionPointer(
&[ &[
Layout::Builtin(Builtin::Int64),
Layout::Builtin(Builtin::Int64),
],
&Layout::Builtin(Builtin::Int64),
),
args: &[
(Int(3), Layout::Builtin(Builtin::Int64)), (Int(3), Layout::Builtin(Builtin::Int64)),
(Int(5), Layout::Builtin(Builtin::Int64)), (Int(5), Layout::Builtin(Builtin::Int64)),
], ],
), },
); );
} }
@ -133,20 +154,31 @@ mod test_mono {
"#, "#,
{ {
use self::Builtin::*; use self::Builtin::*;
use Layout::Builtin;
let home = test_home(); let home = test_home();
let gen_symbol_0 = Interns::from_index(home, 0);
let gen_symbol_3 = Interns::from_index(home, 3);
let gen_symbol_4 = Interns::from_index(home, 4);
Struct(&[ Struct(&[
( (
CallByName(gen_symbol_3, &[(Int(4), Builtin(Int64))]), CallByName {
Builtin(Int64), name: gen_symbol_0,
layout: Layout::FunctionPointer(
&[Layout::Builtin(Builtin::Int64)],
&Layout::Builtin(Builtin::Int64),
),
args: &[(Int(4), Layout::Builtin(Int64))],
},
Layout::Builtin(Int64),
), ),
( (
CallByName(gen_symbol_4, &[(Float(3.14), Builtin(Float64))]), CallByName {
Builtin(Float64), name: gen_symbol_0,
layout: Layout::FunctionPointer(
&[Layout::Builtin(Builtin::Float64)],
&Layout::Builtin(Builtin::Float64),
),
args: &[(Float(3.14), Layout::Builtin(Float64))],
},
Layout::Builtin(Float64),
), ),
]) ])
}, },
@ -314,22 +346,31 @@ mod test_mono {
"#, "#,
{ {
use self::Builtin::*; use self::Builtin::*;
use Layout::Builtin;
let home = test_home(); let home = test_home();
let gen_symbol_3 = Interns::from_index(home, 3); let gen_symbol_0 = Interns::from_index(home, 0);
let gen_symbol_4 = Interns::from_index(home, 4);
CallByName( CallByName {
gen_symbol_3, name: gen_symbol_0,
&[( layout: Layout::FunctionPointer(
&[Layout::Struct(&[Layout::Builtin(Builtin::Int64)])],
&Layout::Struct(&[Layout::Builtin(Builtin::Int64)]),
),
args: &[(
Struct(&[( Struct(&[(
CallByName(gen_symbol_4, &[(Int(4), Builtin(Int64))]), CallByName {
Builtin(Int64), name: gen_symbol_0,
layout: Layout::FunctionPointer(
&[Layout::Builtin(Builtin::Int64)],
&Layout::Builtin(Builtin::Int64),
),
args: &[(Int(4), Layout::Builtin(Int64))],
},
Layout::Builtin(Int64),
)]), )]),
Layout::Struct(&[Builtin(Int64)]), Layout::Struct(&[Layout::Builtin(Int64)]),
)], )],
) }
}, },
) )
} }
@ -464,13 +505,30 @@ mod test_mono {
#[test] #[test]
fn set_unique_int_list() { fn set_unique_int_list() {
compiles_to("List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1", { compiles_to("List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1", {
CallByName( CallByName {
Symbol::LIST_GET_UNSAFE, name: Symbol::LIST_GET_UNSAFE,
&vec![ layout: Layout::FunctionPointer(
&[
Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
Layout::Builtin(Builtin::Int64),
],
&Layout::Builtin(Builtin::Int64),
),
args: &vec![
( (
CallByName( CallByName {
Symbol::LIST_SET, name: Symbol::LIST_SET,
&vec![ layout: Layout::FunctionPointer(
&[
Layout::Builtin(Builtin::List(&Layout::Builtin(
Builtin::Int64,
))),
Layout::Builtin(Builtin::Int64),
Layout::Builtin(Builtin::Int64),
],
&Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
),
args: &vec![
( (
Array { Array {
elem_layout: Layout::Builtin(Builtin::Int64), elem_layout: Layout::Builtin(Builtin::Int64),
@ -483,12 +541,12 @@ mod test_mono {
(Int(1), Layout::Builtin(Builtin::Int64)), (Int(1), Layout::Builtin(Builtin::Int64)),
(Int(42), Layout::Builtin(Builtin::Int64)), (Int(42), Layout::Builtin(Builtin::Int64)),
], ],
), },
Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))), Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
), ),
(Int(1), Layout::Builtin(Builtin::Int64)), (Int(1), Layout::Builtin(Builtin::Int64)),
], ],
) }
}); });
} }

View file

@ -79,8 +79,15 @@ mod test_opt {
unexpected_calls: &mut Vec<Symbol>, unexpected_calls: &mut Vec<Symbol>,
) { ) {
match expr { match expr {
Int(_) | Float(_) | Str(_) | Bool(_) | Byte(_) | Load(_) | FunctionPointer(_) Int(_)
| RuntimeError(_) => (), | Float(_)
| Str(_)
| Bool(_)
| Byte(_)
| Load(_)
| FunctionPointer(_, _)
| RuntimeError(_)
| RuntimeErrorFunction(_) => (),
Store(paths, sub_expr) => { Store(paths, sub_expr) => {
for (_, _, path_expr) in paths.iter() { for (_, _, path_expr) in paths.iter() {
@ -98,15 +105,19 @@ mod test_opt {
} }
} }
CallByName(symbol, args) => { CallByName {
name,
layout: _,
args,
} => {
// Search for the symbol. If we found it, check it off the list. // Search for the symbol. If we found it, check it off the list.
// If we didn't find it, add it to the list of unexpected calls. // If we didn't find it, add it to the list of unexpected calls.
match calls.binary_search(symbol) { match calls.binary_search(name) {
Ok(index) => { Ok(index) => {
calls.remove(index); calls.remove(index);
} }
Err(_) => { Err(_) => {
unexpected_calls.push(*symbol); unexpected_calls.push(*name);
} }
} }
@ -222,13 +233,30 @@ mod test_opt {
// This should optimize List.set to List.set_in_place // This should optimize List.set to List.set_in_place
compiles_to( compiles_to(
"List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1", "List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1",
CallByName( CallByName {
Symbol::LIST_GET_UNSAFE, name: Symbol::LIST_GET_UNSAFE,
&vec![ layout: Layout::FunctionPointer(
&[
Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
Layout::Builtin(Builtin::Int64),
],
&Layout::Builtin(Builtin::Int64),
),
args: &vec![
( (
CallByName( CallByName {
Symbol::LIST_SET_IN_PLACE, name: Symbol::LIST_SET_IN_PLACE,
&vec![ layout: Layout::FunctionPointer(
&[
Layout::Builtin(Builtin::List(&Layout::Builtin(
Builtin::Int64,
))),
Layout::Builtin(Builtin::Int64),
Layout::Builtin(Builtin::Int64),
],
&Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
),
args: &vec![
( (
Array { Array {
elem_layout: Layout::Builtin(Builtin::Int64), elem_layout: Layout::Builtin(Builtin::Int64),
@ -241,12 +269,12 @@ mod test_opt {
(Int(1), Layout::Builtin(Builtin::Int64)), (Int(1), Layout::Builtin(Builtin::Int64)),
(Int(42), Layout::Builtin(Builtin::Int64)), (Int(42), Layout::Builtin(Builtin::Int64)),
], ],
), },
Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))), Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
), ),
(Int(1), Layout::Builtin(Builtin::Int64)), (Int(1), Layout::Builtin(Builtin::Int64)),
], ],
), },
); );
} }

View file

@ -562,250 +562,6 @@ pub enum FlatType {
Boolean(boolean_algebra::Bool), Boolean(boolean_algebra::Bool),
} }
#[derive(Clone, Debug, Hash, PartialEq, Eq, Copy)]
pub struct ContentHash(u64);
impl ContentHash {
pub fn from_var(var: Variable, subs: &mut Subs) -> Self {
use std::hash::Hasher;
let mut hasher = std::collections::hash_map::DefaultHasher::new();
Self::from_var_help(var, subs, &mut hasher);
ContentHash(hasher.finish())
}
pub fn from_var_help<T>(var: Variable, subs: &mut Subs, hasher: &mut T)
where
T: std::hash::Hasher,
{
Self::from_content_help(var, &subs.get_without_compacting(var).content, subs, hasher)
}
pub fn from_content_help<T>(var: Variable, content: &Content, subs: &mut Subs, hasher: &mut T)
where
T: std::hash::Hasher,
{
match content {
Content::Alias(_, _, actual) => {
// ensure an alias has the same hash as just the body of the alias
Self::from_var_help(*actual, subs, hasher)
}
Content::Structure(flat_type) => {
hasher.write_u8(0x10);
Self::from_flat_type_help(var, flat_type, subs, hasher)
}
Content::FlexVar(_) | Content::RigidVar(_) => {
hasher.write_u8(0x11);
}
Content::Error => {
hasher.write_u8(0x12);
}
}
}
pub fn from_flat_type_help<T>(
flat_type_var: Variable,
flat_type: &FlatType,
subs: &mut Subs,
hasher: &mut T,
) where
T: std::hash::Hasher,
{
use std::hash::Hash;
match flat_type {
FlatType::Func(arguments, ret) => {
hasher.write_u8(0);
for var in arguments {
Self::from_var_help(*var, subs, hasher);
}
Self::from_var_help(*ret, subs, hasher);
}
FlatType::Apply(symbol, arguments) => {
hasher.write_u8(1);
symbol.hash(hasher);
for var in arguments {
Self::from_var_help(*var, subs, hasher);
}
}
FlatType::EmptyRecord => {
hasher.write_u8(2);
}
FlatType::Record(record_fields, ext) => {
hasher.write_u8(3);
// NOTE: This function will modify the subs, putting all fields from the ext_var
// into the record itself, then setting the ext_var to EMPTY_RECORD
let mut fields = Vec::with_capacity(record_fields.len());
let mut extracted_fields_from_ext = false;
if *ext != Variable::EMPTY_RECORD {
let mut fields_map = MutMap::default();
match crate::pretty_print::chase_ext_record(subs, *ext, &mut fields_map) {
Err((_, Content::FlexVar(_))) | Ok(()) => {
if !fields_map.is_empty() {
extracted_fields_from_ext = true;
fields.extend(fields_map.into_iter());
}
}
Err(content) => panic!("Record with unexpected ext_var: {:?}", content),
}
}
fields.extend(record_fields.clone().into_iter());
fields.sort();
for (name, argument) in &fields {
name.hash(hasher);
Self::from_var_help(*argument, subs, hasher);
}
if *ext != Variable::EMPTY_RECORD {
// unify ext with empty record
let desc = subs.get(Variable::EMPTY_RECORD);
subs.union(Variable::EMPTY_RECORD, *ext, desc);
}
if extracted_fields_from_ext {
let fields_map = fields.into_iter().collect();
subs.set_content(
flat_type_var,
Content::Structure(FlatType::Record(fields_map, Variable::EMPTY_RECORD)),
);
}
}
FlatType::EmptyTagUnion => {
hasher.write_u8(4);
}
FlatType::TagUnion(tags, ext) => {
hasher.write_u8(5);
// NOTE: This function will modify the subs, putting all tags from the ext_var
// into the tag union itself, then setting the ext_var to EMPTY_TAG_UNION
let mut tag_vec = Vec::with_capacity(tags.len());
let mut extracted_fields_from_ext = false;
if *ext != Variable::EMPTY_TAG_UNION {
match crate::pretty_print::chase_ext_tag_union(subs, *ext, &mut tag_vec) {
Err((_, Content::FlexVar(_))) | Ok(()) => {
extracted_fields_from_ext = !tag_vec.is_empty();
}
Err(content) => panic!("TagUnion with unexpected ext_var: {:?}", content),
}
}
tag_vec.extend(tags.clone().into_iter());
tag_vec.sort();
for (name, arguments) in &tag_vec {
name.hash(hasher);
for var in arguments {
Self::from_var_help(*var, subs, hasher);
}
}
if *ext != Variable::EMPTY_TAG_UNION {
// unify ext with empty record
let desc = subs.get(Variable::EMPTY_TAG_UNION);
subs.union(Variable::EMPTY_TAG_UNION, *ext, desc);
}
if extracted_fields_from_ext {
let fields_map = tag_vec.into_iter().collect();
subs.set_content(
flat_type_var,
Content::Structure(FlatType::TagUnion(
fields_map,
Variable::EMPTY_TAG_UNION,
)),
);
}
}
FlatType::RecursiveTagUnion(rec, tags, ext) => {
// NOTE: rec is not hashed in. If all the tags and their arguments are the same,
// then the recursive tag unions are the same
hasher.write_u8(6);
// NOTE: This function will modify the subs, putting all tags from the ext_var
// into the tag union itself, then setting the ext_var to EMPTY_TAG_UNION
let mut tag_vec = Vec::with_capacity(tags.len());
let mut extracted_fields_from_ext = false;
if *ext != Variable::EMPTY_TAG_UNION {
match crate::pretty_print::chase_ext_tag_union(subs, *ext, &mut tag_vec) {
Err((_, Content::FlexVar(_))) | Ok(()) => {
extracted_fields_from_ext = !tag_vec.is_empty();
}
Err(content) => {
panic!("RecursiveTagUnion with unexpected ext_var: {:?}", content)
}
}
}
tag_vec.extend(tags.clone().into_iter());
tag_vec.sort();
for (name, arguments) in &tag_vec {
name.hash(hasher);
for var in arguments {
Self::from_var_help(*var, subs, hasher);
}
}
if *ext != Variable::EMPTY_TAG_UNION {
// unify ext with empty record
let desc = subs.get(Variable::EMPTY_TAG_UNION);
subs.union(Variable::EMPTY_TAG_UNION, *ext, desc);
}
if extracted_fields_from_ext {
let fields_map = tag_vec.into_iter().collect();
subs.set_content(
flat_type_var,
Content::Structure(FlatType::RecursiveTagUnion(
*rec,
fields_map,
Variable::EMPTY_TAG_UNION,
)),
);
}
}
FlatType::Boolean(boolean) => {
hasher.write_u8(7);
match boolean.simplify(subs) {
Ok(_variables) => hasher.write_u8(1),
Err(crate::boolean_algebra::Atom::One) => hasher.write_u8(1),
Err(crate::boolean_algebra::Atom::Zero) => hasher.write_u8(0),
Err(crate::boolean_algebra::Atom::Variable(_)) => unreachable!(),
}
}
FlatType::Erroneous(_problem) => {
hasher.write_u8(8);
//TODO hash the problem?
}
}
}
}
#[derive(PartialEq, Eq, Debug, Clone, Copy)] #[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum Builtin { pub enum Builtin {
Str, Str,

View file

@ -1,8 +1,10 @@
#version 450 #version 450
#extension GL_ARB_separate_shader_objects : enable #extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec4 vertex_color;
layout(location = 0) out vec4 fragment_color; layout(location = 0) out vec4 fragment_color;
void main() { void main() {
fragment_color = vec4(0.5, 0.5, 1.0, 1.0); fragment_color = vertex_color;
} }

View file

@ -1,6 +1,14 @@
#version 450 #version 450
#extension GL_ARB_separate_shader_objects : enable #extension GL_ARB_separate_shader_objects : enable
layout(push_constant) uniform PushConstants {
vec4 color;
vec2 pos;
vec2 scale;
} push_constants;
layout(location = 0) out vec4 vertex_color;
void main() { void main() {
vec2 position; vec2 position;
if (gl_VertexIndex == 0) { if (gl_VertexIndex == 0) {
@ -11,5 +19,7 @@ void main() {
position = vec2(0.5, 0.5); position = vec2(0.5, 0.5);
} }
gl_Position = vec4(position, 0.0, 1.0); vec2 pos = position * push_constants.scale;
vertex_color = push_constants.color;
gl_Position = vec4((pos + push_constants.pos), 0.0, 1.0);
} }

View file

@ -7,6 +7,7 @@ use glsl_to_spirv::ShaderType;
use std::io; use std::io;
use std::mem::ManuallyDrop; use std::mem::ManuallyDrop;
use std::path::Path; use std::path::Path;
use winit::event::{ElementState, ModifiersState, VirtualKeyCode};
/// The editor is actually launched from the CLI if you pass it zero arguments, /// The editor is actually launched from the CLI if you pass it zero arguments,
/// or if you provide it 1 or more files or directories to open on launch. /// or if you provide it 1 or more files or directories to open on launch.
@ -67,10 +68,23 @@ impl<B: gfx_hal::Backend> Drop for ResourceHolder<B> {
} }
} }
#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct PushConstants {
color: [f32; 4],
pos: [f32; 2],
scale: [f32; 2],
}
fn run_event_loop() { fn run_event_loop() {
// TODO do a better window size // TODO do a better window size
const WINDOW_SIZE: [u32; 2] = [512, 512]; const WINDOW_SIZE: [u32; 2] = [512, 512];
// TODO try configuring the swapchain explicitly, in particular in order
// to experiment with different PresentMode settings to see how they
// affect input latency.
//
// https://rust-tutorials.github.io/learn-gfx-hal/03_clear_the_window.html
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
let (logical_window_size, physical_window_size) = { let (logical_window_size, physical_window_size) = {
@ -191,8 +205,12 @@ fn run_event_loop() {
}; };
let pipeline_layout = unsafe { let pipeline_layout = unsafe {
use gfx_hal::pso::ShaderStageFlags;
let push_constant_bytes = std::mem::size_of::<PushConstants>() as u32;
device device
.create_pipeline_layout(&[], &[]) .create_pipeline_layout(&[], &[(ShaderStageFlags::VERTEX, 0..push_constant_bytes)])
.expect("Out of memory") .expect("Out of memory")
}; };
@ -295,16 +313,21 @@ fn run_event_loop() {
submission_complete_fence, submission_complete_fence,
rendering_complete_semaphore, rendering_complete_semaphore,
})); }));
let is_animating = true;
let mut text_state = String::new();
let mut keyboard_modifiers = ModifiersState::empty();
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, _, control_flow| {
use winit::event::{Event, WindowEvent}; use winit::event::{Event, WindowEvent};
use winit::event_loop::ControlFlow; use winit::event_loop::ControlFlow;
// TODO try ControlFlow::Poll and see if it affects input latency. // TODO dynamically switch this on/off depending on whether any
// Otherwise, this seems like a better default for minimizing idle // animations are running. Should conserve CPU usage and battery life!
// CPU usage and battry drain. (Might want to switch to Poll whenever if is_animating {
// there are animations in progress though.) *control_flow = ControlFlow::Poll;
} else {
*control_flow = ControlFlow::Wait; *control_flow = ControlFlow::Wait;
}
match event { match event {
Event::WindowEvent { Event::WindowEvent {
@ -322,6 +345,16 @@ fn run_event_loop() {
}; };
should_configure_swapchain = true; should_configure_swapchain = true;
} }
WindowEvent::KeyboardInput { input, .. } => {
if let Some(virtual_keycode) = input.virtual_keycode {
handle_text_input(
&mut text_state,
input.state,
virtual_keycode,
keyboard_modifiers,
);
}
}
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
surface_extent = Extent2D { surface_extent = Extent2D {
width: new_inner_size.width, width: new_inner_size.width,
@ -329,14 +362,34 @@ fn run_event_loop() {
}; };
should_configure_swapchain = true; should_configure_swapchain = true;
} }
WindowEvent::ModifiersChanged(modifiers) => {
keyboard_modifiers = modifiers;
}
_ => (), _ => (),
}, },
Event::MainEventsCleared => window.request_redraw(), Event::MainEventsCleared => window.request_redraw(),
Event::RedrawRequested(_) => { Event::RedrawRequested(_) => {
let res: &mut Resources<_> = &mut resource_holder.0; let res: &mut Resources<_> = &mut resource_holder.0;
let render_pass = &res.render_passes[0]; let render_pass = &res.render_passes[0];
let pipeline_layout = &res.pipeline_layouts[0];
let pipeline = &res.pipelines[0]; let pipeline = &res.pipelines[0];
let triangles = text_state.chars().enumerate().map(|(index, char)| {
if char == ' ' {
PushConstants {
color: [0.0, 0.0, 0.0, 0.0],
pos: [0.0, 0.0],
scale: [0.00, 0.00],
}
} else {
PushConstants {
color: [1.0, 1.0, 1.0, 1.0],
pos: [0.06 * index as f32, 0.0],
scale: [0.05, 0.05],
}
}
});
unsafe { unsafe {
use gfx_hal::pool::CommandPool; use gfx_hal::pool::CommandPool;
@ -444,7 +497,20 @@ fn run_event_loop() {
SubpassContents::Inline, SubpassContents::Inline,
); );
command_buffer.bind_graphics_pipeline(pipeline); command_buffer.bind_graphics_pipeline(pipeline);
for triangle in triangles {
use gfx_hal::pso::ShaderStageFlags;
command_buffer.push_graphics_constants(
pipeline_layout,
ShaderStageFlags::VERTEX,
0,
push_constant_bytes(&triangle),
);
command_buffer.draw(0..3, 0..1); command_buffer.draw(0..3, 0..1);
}
command_buffer.end_render_pass(); command_buffer.end_render_pass();
command_buffer.finish(); command_buffer.finish();
} }
@ -488,3 +554,139 @@ fn compile_shader(glsl: &str, shader_type: ShaderType) -> Vec<u32> {
gfx_hal::pso::read_spirv(Cursor::new(&spirv_bytes)).expect("Invalid SPIR-V") gfx_hal::pso::read_spirv(Cursor::new(&spirv_bytes)).expect("Invalid SPIR-V")
} }
/// Returns a view of a struct as a slice of `u32`s.
unsafe fn push_constant_bytes<T>(push_constants: &T) -> &[u32] {
let size_in_bytes = std::mem::size_of::<T>();
let size_in_u32s = size_in_bytes / std::mem::size_of::<u32>();
let start_ptr = push_constants as *const T as *const u32;
std::slice::from_raw_parts(start_ptr, size_in_u32s)
}
fn handle_text_input(
text_state: &mut String,
elem_state: ElementState,
virtual_keycode: VirtualKeyCode,
_modifiers: ModifiersState,
) {
use winit::event::VirtualKeyCode::*;
if let ElementState::Released = elem_state {
return;
}
match virtual_keycode {
Key1 | Numpad1 => text_state.push_str("1"),
Key2 | Numpad2 => text_state.push_str("2"),
Key3 | Numpad3 => text_state.push_str("3"),
Key4 | Numpad4 => text_state.push_str("4"),
Key5 | Numpad5 => text_state.push_str("5"),
Key6 | Numpad6 => text_state.push_str("6"),
Key7 | Numpad7 => text_state.push_str("7"),
Key8 | Numpad8 => text_state.push_str("8"),
Key9 | Numpad9 => text_state.push_str("9"),
Key0 | Numpad0 => text_state.push_str("0"),
A => text_state.push_str("a"),
B => text_state.push_str("b"),
C => text_state.push_str("c"),
D => text_state.push_str("d"),
E => text_state.push_str("e"),
F => text_state.push_str("f"),
G => text_state.push_str("g"),
H => text_state.push_str("h"),
I => text_state.push_str("i"),
J => text_state.push_str("j"),
K => text_state.push_str("k"),
L => text_state.push_str("l"),
M => text_state.push_str("m"),
N => text_state.push_str("n"),
O => text_state.push_str("o"),
P => text_state.push_str("p"),
Q => text_state.push_str("q"),
R => text_state.push_str("r"),
S => text_state.push_str("s"),
T => text_state.push_str("t"),
U => text_state.push_str("u"),
V => text_state.push_str("v"),
W => text_state.push_str("w"),
X => text_state.push_str("x"),
Y => text_state.push_str("y"),
Z => text_state.push_str("z"),
Escape | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9 | F10 | F11 | F12 | F13 | F14 | F15
| F16 | F17 | F18 | F19 | F20 | F21 | F22 | F23 | F24 | Snapshot | Scroll | Pause
| Insert | Home | Delete | End | PageDown | PageUp | Left | Up | Right | Down | Compose
| Caret | Numlock | AbntC1 | AbntC2 | Ax | Calculator | Capital | Convert | Kana
| Kanji | LAlt | LBracket | LControl | LShift | LWin | Mail | MediaSelect | PlayPause
| Power | PrevTrack | MediaStop | Mute | MyComputer | NavigateForward
| NavigateBackward | NextTrack | NoConvert | OEM102 | RAlt | Sysrq | RBracket
| RControl | RShift | RWin | Sleep | Stop | Unlabeled | VolumeDown | VolumeUp | Wake
| WebBack | WebFavorites | WebForward | WebHome | WebRefresh | WebSearch | Apps | Tab
| WebStop => {
// TODO handle
dbg!(virtual_keycode);
}
Back => {
text_state.pop();
}
Return | NumpadEnter => {
text_state.push_str("\n");
}
Space => {
text_state.push_str(" ");
}
Comma | NumpadComma => {
text_state.push_str(",");
}
Add => {
text_state.push_str("+");
}
Apostrophe => {
text_state.push_str("'");
}
At => {
text_state.push_str("@");
}
Backslash => {
text_state.push_str("\\");
}
Colon => {
text_state.push_str(":");
}
Period | Decimal => {
text_state.push_str(".");
}
Equals | NumpadEquals => {
text_state.push_str("=");
}
Grave => {
text_state.push_str("`");
}
Minus | Subtract => {
text_state.push_str("-");
}
Multiply => {
text_state.push_str("*");
}
Semicolon => {
text_state.push_str(";");
}
Slash | Divide => {
text_state.push_str("/");
}
Underline => {
text_state.push_str("_");
}
Yen => {
text_state.push_str("¥");
}
Copy => {
todo!("copy");
}
Paste => {
todo!("paste");
}
Cut => {
todo!("cut");
}
}
}

View file

@ -1,7 +1,7 @@
{ pkgs }: [ { pkgs }: [
pkgs.rustup pkgs.rustup
pkgs.cargo pkgs.cargo
pkgs.llvm_8 pkgs.llvm_10
# libraries for llvm # libraries for llvm
pkgs.libffi pkgs.libffi
pkgs.libxml2 pkgs.libxml2

View file

@ -225,7 +225,95 @@ when color is
## Custom Types ## Custom Types
This is the biggest difference between Roc and Elm. This is the biggest semantic difference between Roc and Elm.
Let's start with the motivation. Suppose I'm using a platform for making a
web server, and I want to:
* Read some data from a file
* Send a HTTP request containing some of the data from the file
* Write some data to a file containing some of the data from the HTTP response
Assuming I'm writing this on a Roc platform which has a `Task`-based API,
here's how that code might look:
```elm
doStuff = \filename ->
Task.after (File.read filename) \fileData ->
Task.after (Http.get (urlFromData fileData)) \response ->
File.write filename (responseToData response)
```
A few things to note before getting into how this relates to custom types:
1. This is written in a style designed for chained effects. It's kinda like [`do` notation](https://en.wikibooks.org/wiki/Haskell/do_notation), but implemented as a formatting convention instead of special syntax.
2. In Elm you'd need to add a `<|` before the anonymous functions (e.g. `<| \response ->`) but in Roc you don't. (That parsing design decision was partly motivated by supporting this style of chained effects.)
3. `Task.after` is `Task.andThen` with its arguments flipped.
What would the type of the above expression be? Let's say these function calls
have the following types:
```elm
File.read : Filename -> Task File.Data File.ReadErr
File.write : Filename, File.Data -> Task File.Data File.WriteErr
Http.get : Url -> Task Http.Response Http.Err
after : Task a err, (a -> Task b err) -> Task b err
```
If these are the types, the result would be a type mismatch. Those `Task` values
have incompatible error types, so `after` won't be able to chain them together.
This situation is one of the motivations behind Roc's *tags* feature. Using tags,
not only will this type-check, but at the end we get a combined error type which
has the union of all the possible errors that could have occurred in this sequence.
We can then handle those errors using a single `when`, like so:
```elm
when error is
# Http.Err possibilities
PageNotFound -> ...
Timeout -> ...
BadPayload -> ...
# File.ReadErr possibilities
FileNotFound -> ...
ReadAcessDenied -> ...
FileCorrupted -> ...
# File.WriteErr possibilities
DirectoryNotFound -> ...
WriteAcessDenied -> ...
DiskFull -> ...
```
Here is a set
of slightly different types that would make the above expression compile.
(`after` is unchanged.)
```elm
File.read : Filename -> Task File.Data (File.ReadErr *)
File.write : Filename, File.Data -> Task File.Data (File.WriteErr *)
Http.get : Url -> Task Http.Response (Http.Err *)
after : Task a err, (a -> Task b err) -> Task b err
```
The key is that each of the error types expands to a Roc *tag union*. Here's how
they look:
```elm
Http.Err a : [PageNotFound, Timeout, BadPayload]a
File.ReadErr a : [FileNotFound, Corrupted, BadFormat]a
File.WriteErr a : [FileNotFound, DiskFull]a
```
```elm
first : List elem -> [Ok elem, ListWasEmpty]*
```
> It's motivated primarily by error handling in chained effects > It's motivated primarily by error handling in chained effects
> (e.g. multiple consecutive `Task.andThen`s between tasks with incompatible error types), > (e.g. multiple consecutive `Task.andThen`s between tasks with incompatible error types),