mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 06:14:46 +00:00
Merge branch 'trunk' into int-rem
This commit is contained in:
commit
7f05678bf8
25 changed files with 1508 additions and 842 deletions
|
@ -1,5 +1,6 @@
|
|||
#[macro_use]
|
||||
extern crate clap;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use inkwell::context::Context;
|
||||
use inkwell::module::Linkage;
|
||||
|
@ -8,6 +9,7 @@ use inkwell::types::BasicType;
|
|||
use inkwell::OptimizationLevel;
|
||||
use roc_collections::all::ImMap;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_gen::layout_id::LayoutIds;
|
||||
use roc_gen::llvm::build::{
|
||||
build_proc, build_proc_header, get_call_conventions, module_from_builtins, OptLevel,
|
||||
};
|
||||
|
@ -358,7 +360,7 @@ fn gen(
|
|||
|
||||
// Compute main_fn_type before moving subs to Env
|
||||
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
||||
let layout = Layout::from_content(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| {
|
||||
let layout = Layout::new(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}",
|
||||
err, subs
|
||||
|
@ -378,9 +380,10 @@ fn gen(
|
|||
module: arena.alloc(module),
|
||||
ptr_bytes,
|
||||
};
|
||||
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
|
||||
let mut layout_ids = LayoutIds::default();
|
||||
let mut procs = Procs::default();
|
||||
let mut mono_problems = std::vec::Vec::new();
|
||||
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
|
||||
let mut mono_env = Env {
|
||||
arena,
|
||||
subs: &mut subs,
|
||||
|
@ -451,13 +454,17 @@ fn gen(
|
|||
env.interns.all_ident_ids.insert(home, ident_ids);
|
||||
|
||||
let mut headers = Vec::with_capacity(procs.len());
|
||||
let (mut proc_map, runtime_errors) = procs.into_map();
|
||||
|
||||
assert_eq!(runtime_errors, roc_collections::all::MutSet::default());
|
||||
|
||||
// Add all the Proc headers to the module.
|
||||
// We have to do this in a separate pass first,
|
||||
// because their bodies may reference each other.
|
||||
for (symbol, opt_proc) in procs.as_map().into_iter() {
|
||||
if let Some(proc) = opt_proc {
|
||||
let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc);
|
||||
for (symbol, mut procs_by_layout) in proc_map.drain() {
|
||||
for (layout, proc) in procs_by_layout.drain() {
|
||||
let (fn_val, arg_basic_types) =
|
||||
build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
|
||||
|
||||
headers.push((proc, fn_val, arg_basic_types));
|
||||
}
|
||||
|
@ -469,7 +476,7 @@ fn gen(
|
|||
// (This approach means we don't have to defensively clone name here.)
|
||||
//
|
||||
// println!("\n\nBuilding and then verifying function {}\n\n", name);
|
||||
build_proc(&env, proc, &procs, fn_val, arg_basic_types);
|
||||
build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types);
|
||||
|
||||
if fn_val.verify(true) {
|
||||
fpm.run_on(&fn_val);
|
||||
|
@ -495,10 +502,10 @@ fn gen(
|
|||
|
||||
let ret = roc_gen::llvm::build::build_expr(
|
||||
&env,
|
||||
&mut layout_ids,
|
||||
&ImMap::default(),
|
||||
main_fn,
|
||||
&main_body,
|
||||
&Procs::default(),
|
||||
);
|
||||
|
||||
builder.build_return(Some(&ret));
|
||||
|
|
|
@ -14,6 +14,7 @@ use roc_can::scope::Scope;
|
|||
use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet};
|
||||
use roc_constrain::expr::constrain_expr;
|
||||
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
|
||||
use roc_gen::layout_id::LayoutIds;
|
||||
use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel};
|
||||
use roc_gen::llvm::convert::basic_type_from_layout;
|
||||
use roc_module::ident::Ident;
|
||||
|
@ -218,7 +219,7 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St
|
|||
let expr_type_str = content_to_string(content.clone(), &subs, home, &interns);
|
||||
|
||||
// Compute main_fn_type before moving subs to Env
|
||||
let layout = Layout::from_content(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| {
|
||||
let layout = Layout::new(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}",
|
||||
err, subs
|
||||
|
@ -243,6 +244,7 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St
|
|||
};
|
||||
let mut procs = Procs::default();
|
||||
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
|
||||
let mut layout_ids = LayoutIds::default();
|
||||
|
||||
// Populate Procs and get the low-level Expr from the canonical Expr
|
||||
let mut mono_problems = Vec::new();
|
||||
|
@ -261,13 +263,21 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St
|
|||
env.interns.all_ident_ids.insert(home, ident_ids);
|
||||
|
||||
let mut headers = Vec::with_capacity(procs.len());
|
||||
let (mut proc_map, runtime_errors) = procs.into_map();
|
||||
|
||||
assert_eq!(
|
||||
runtime_errors,
|
||||
roc_collections::all::MutSet::default(),
|
||||
"TODO code gen runtime exception functions"
|
||||
);
|
||||
|
||||
// Add all the Proc headers to the module.
|
||||
// We have to do this in a separate pass first,
|
||||
// because their bodies may reference each other.
|
||||
for (symbol, opt_proc) in procs.as_map().into_iter() {
|
||||
if let Some(proc) = opt_proc {
|
||||
let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc);
|
||||
for (symbol, mut procs_by_layout) in proc_map.drain() {
|
||||
for (layout, proc) in procs_by_layout.drain() {
|
||||
let (fn_val, arg_basic_types) =
|
||||
build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
|
||||
|
||||
headers.push((proc, fn_val, arg_basic_types));
|
||||
}
|
||||
|
@ -279,7 +289,7 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St
|
|||
// (This approach means we don't have to defensively clone name here.)
|
||||
//
|
||||
// println!("\n\nBuilding and then verifying function {}\n\n", name);
|
||||
build_proc(&env, proc, &procs, fn_val, arg_basic_types);
|
||||
build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types);
|
||||
|
||||
if fn_val.verify(true) {
|
||||
fpm.run_on(&fn_val);
|
||||
|
@ -305,10 +315,10 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St
|
|||
|
||||
let ret = roc_gen::llvm::build::build_expr(
|
||||
&env,
|
||||
&mut layout_ids,
|
||||
&ImMap::default(),
|
||||
main_fn,
|
||||
&main_body,
|
||||
&Procs::default(),
|
||||
);
|
||||
|
||||
builder.build_return(Some(&ret));
|
||||
|
|
|
@ -4,9 +4,9 @@ interface List
|
|||
|
||||
## 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
|
||||
##
|
||||
|
@ -20,29 +20,43 @@ interface List
|
|||
## 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.
|
||||
## > Small lists are stored as flat arrays. This "small list optimization"
|
||||
## > applies to lists that take up 8 machine words in memory or fewer, so
|
||||
## > for example on a 64-bit system, a list of 8 #Int values will be
|
||||
## > stored as a flat array instead of as an RRBT.
|
||||
## > The theoretical maximum length for a list created in Roc is
|
||||
## > #Int.highestUlen divided by 2. Attempting to create a list bigger than that
|
||||
## > in Roc code will always fail, although in practice it is likely to fail
|
||||
## > at much smaller lengths due to insufficient memory being available.
|
||||
##
|
||||
## One #List can store up to 2,147,483,648 elements (just over 2 billion). If you need to store more
|
||||
## 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,
|
||||
## even for strings much smaller than 2 gigabytes.
|
||||
## ## Performance notes
|
||||
##
|
||||
## Under the hood, a list is a record containing a `len : Ulen` field as well
|
||||
## 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
|
||||
|
||||
## Initialize
|
||||
|
||||
single : elem -> List elem
|
||||
|
||||
## If given any number less than 1, returns #[].
|
||||
repeat : elem, Int -> List elem
|
||||
empty : List *
|
||||
|
||||
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
|
||||
## only that value.
|
||||
##
|
||||
|
@ -59,8 +73,7 @@ fromResult : Result elem * -> List elem
|
|||
|
||||
reverse : List elem -> List elem
|
||||
|
||||
sort : List elem, (elem, elem -> [ Eq, Lt, Gt ]) -> List elem
|
||||
sortBy : List elem, (elem -> field), (field, field -> [ Eq, Lt, Gt ]) -> List elem
|
||||
sort : List elem, Sorter elem -> List elem
|
||||
|
||||
## 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.
|
||||
|
@ -123,7 +136,7 @@ joinMap : List before, (before -> List after) -> List after
|
|||
## >>> |> List.joinOks
|
||||
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
|
||||
## in the same position in # the other lists.
|
||||
##
|
||||
|
@ -131,12 +144,12 @@ joinOks : List (Result elem *) -> List elem
|
|||
##
|
||||
## 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`.
|
||||
zip :
|
||||
List a, List b, -> List [ Tup a b ]*
|
||||
List a, List b, List c, -> List [ Tup a b c ]*
|
||||
List a, List b, List c, List d -> List [ Tup a b c d ]*
|
||||
List a, List b, -> List [ Pair a b ]*
|
||||
List a, List b, List c, -> List [ Pair a b c ]*
|
||||
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.
|
||||
##
|
||||
|
@ -157,13 +170,35 @@ zipMap :
|
|||
## elements for which the function returned `True`.
|
||||
##
|
||||
## >>> 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
|
||||
## elements for which the function returned `False`.
|
||||
##
|
||||
## >>> 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
|
||||
## and returns them.
|
||||
|
@ -187,6 +222,22 @@ take : List elem, Int -> List elem
|
|||
## >>> drop 5 [ 1, 2 ]
|
||||
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
|
||||
|
||||
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
|
||||
## 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.
|
||||
len : List * -> U32
|
||||
len : List * -> Ulen
|
||||
|
||||
isEmpty : List * -> Bool
|
||||
|
||||
|
@ -211,4 +262,3 @@ contains : List elem, elem -> Bool
|
|||
all : List elem, (elem -> Bool) -> Bool
|
||||
|
||||
any : List elem, (elem -> Bool) -> Bool
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
interface Bool
|
||||
exposes [ Bool, not, equal, notEqual ]
|
||||
exposes [ not, isEq, isNe ]
|
||||
imports []
|
||||
|
||||
## Either #True or #False.
|
||||
Bool : [ False, True ]
|
||||
|
||||
## 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.
|
||||
##
|
||||
|
@ -60,7 +57,7 @@ not : Bool -> Bool
|
|||
## That said, in practice the `&& Str.isEmpty str` approach will typically run
|
||||
## faster than the `&& emptyStr` approach - both for `Str.isEmpty` in particular
|
||||
## 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.
|
||||
|
@ -82,7 +79,10 @@ and : Bool, Bool -> Bool
|
|||
## #True (causing it to immediately returns #True).
|
||||
##
|
||||
## 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.
|
||||
##
|
||||
|
@ -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.
|
||||
## 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.
|
||||
eq : val, val -> Bool
|
||||
isEq : val, val -> [True, False]
|
||||
|
||||
## Calls #eq on the given values, then calls #not on the result.
|
||||
##
|
||||
## This is the same as the #=/= operator.
|
||||
notEq : val, val -> Bool
|
||||
notEq = \left, right ->
|
||||
## This is the same as the #!= operator.
|
||||
isNe : val, val -> [True, False]
|
||||
isNe = \left, right ->
|
||||
not (equal left right)
|
||||
|
||||
|
|
|
@ -37,19 +37,69 @@ interface Float
|
|||
##
|
||||
## >>> 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
|
||||
##
|
||||
## 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
|
||||
## lowest values that can be held in a #Float.
|
||||
## >>> 0.1f64 + 0.2f64
|
||||
##
|
||||
## Like #Int, it's possible for #Float operations to overflow.
|
||||
## if they exceed the bounds of #Float.highest and #Float.lowest. When this happens:
|
||||
## If decimal precision is unimportant, binary floats give better performance.
|
||||
## 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 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
|
||||
## 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!
|
||||
#FloatingPoint := FloatingPoint
|
||||
|
||||
## Returned in an #Err by #Float.sqrt when given a negative number.
|
||||
#InvalidSqrt := InvalidSqrt
|
||||
##
|
||||
## ## Loud versus Quiet errors
|
||||
##
|
||||
## 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
|
||||
|
||||
|
@ -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 #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
|
||||
##
|
||||
|
@ -176,7 +269,22 @@ tryRecip : Float a -> Result (Float a) [ DivByZero ]*
|
|||
## >>> Float.sqrt 0.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
|
||||
|
||||
|
@ -198,17 +306,52 @@ asc : 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
|
||||
|
||||
## 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!
|
||||
highest : Float *
|
||||
maxF64 : Float *
|
||||
|
||||
## 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!
|
||||
lowest : Float *
|
||||
minF64 : Float *
|
||||
|
||||
## 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.
|
||||
|
@ -220,7 +363,7 @@ lowest : Float *
|
|||
## >>> Float.highestInt + 100 # Increasing may lose precision
|
||||
##
|
||||
## >>> 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.
|
||||
## 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 # Increasing is fine - but watch out for highestInt!
|
||||
lowestInt : Float *
|
||||
maxPreciseInt : Float *
|
||||
|
|
|
@ -16,7 +16,7 @@ interface Int
|
|||
## #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.
|
||||
## 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.
|
||||
##
|
||||
## #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.
|
||||
## * 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)
|
||||
|
||||
## A signed 8-bit integer, ranging from -128 to 127
|
||||
|
@ -66,8 +66,8 @@ I64 : Int @I64
|
|||
U64 : Int @U64
|
||||
I128 : Int @I128
|
||||
U128 : Int @U128
|
||||
ILen : Int @ILen
|
||||
ULen : Int @ULen
|
||||
Ilen : Int @Ilen
|
||||
Ulen : Int @Ulen
|
||||
|
||||
## 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!
|
||||
## * 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:
|
||||
##
|
||||
|
@ -127,24 +127,19 @@ ULen : Int @ULen
|
|||
## | ` (over 340 undecillion) 0` | #U128 | 16 Bytes |
|
||||
## | ` 340_282_366_920_938_463_463_374_607_431_768_211_455` | | |
|
||||
##
|
||||
## There are also two variable-size integer types: #Iword and #Uword.
|
||||
## Their sizes are determined by the machine word size for the system you're
|
||||
## compiling for. For example, on a 64-bit system, #Iword is the same as #I64,
|
||||
## and #Uword is the same as #U64.
|
||||
## There are also two variable-size integer types: #Ulen and #Ilen. Their sizes
|
||||
## are determined by the [machine word length](https://en.wikipedia.org/wiki/Word_(computer_architecture))
|
||||
## of the system you're compiling for. (The "len" in their names is short for "length of a machine word.")
|
||||
## 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
|
||||
## or too small to fit in that range (e.g. calling `Int.highest32 + 1`),
|
||||
## then the operation will *overflow* or *underflow*, respectively.
|
||||
##
|
||||
## 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.)
|
||||
## or too small to fit in that range (e.g. calling `Int.maxI32 + 1`),
|
||||
## then the operation will *overflow*. When an overflow occurs, the program will crash.
|
||||
##
|
||||
## 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
|
||||
## a different representation other than #Int. The reason #Int has these
|
||||
## bounds is for performance reasons.
|
||||
## If you need to do math outside these bounds, consider using a larger numeric size.
|
||||
# Int size : Num [ @Int size ]
|
||||
|
||||
## 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?
|
||||
##
|
||||
## 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,
|
||||
## so be aware that if you rely on the exact answer this gives today, your
|
||||
## 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
|
||||
## available memory and crashing.
|
||||
##
|
||||
## Note that this is smaller than the positive version of #Int.lowestI32
|
||||
## which means if you call #Num.abs on #Int.lowestI32, it will overflow and crash!
|
||||
highestI32 : I32
|
||||
## Note that this is smaller than the positive version of #Int.minI32
|
||||
## which means if you call #Num.abs on #Int.minI32, it will overflow and crash!
|
||||
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.
|
||||
##
|
||||
## 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!
|
||||
lowest : I32
|
||||
## #Int.maxI32, which means if you call #Num.abs on #Int.minI32, it will overflow and crash!
|
||||
minI32 : I32
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -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 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`.
|
||||
##
|
||||
## 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`. #
|
||||
## 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
|
||||
|
||||
## Convert
|
||||
|
@ -137,3 +169,19 @@ sub : Num range, Num range -> Num range
|
|||
## >>> Float.pi
|
||||
## >>> |> Num.mul 2.0
|
||||
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
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
api Str provides Str, isEmpty, join
|
||||
interface Str exposes [ Str, isEmpty, join ] imports []
|
||||
|
||||
## 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
|
||||
## 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
|
||||
## functions (including capitalization, as capitalization rules vary by locale)
|
||||
## see the [roc/unicode](roc/unicode) package. For locale-specific text
|
||||
## functions (including capitalizing a string, as capitalization rules vary by locale)
|
||||
## see the [roc/locale](roc/locale) package.
|
||||
##
|
||||
## ### 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).
|
||||
## A grapheme cluster corresponds to what a person reading a string might call
|
||||
## a "character", but because the term "character" is used to mean many different
|
||||
## concepts across different programming languages, we intentionally avoid it in Roc.
|
||||
## Instead, we use the term "clusters" as a shorthand for "grapheme clusters."
|
||||
## A grapheme cluster corresponds to what a person reading a string might call a "character",
|
||||
## but because the term "character" is used to mean many different concepts across
|
||||
## different programming languages, the documentation for Roc strings intentionally
|
||||
## 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:
|
||||
##
|
||||
|
@ -33,13 +33,53 @@ api Str provides Str, isEmpty, join
|
|||
##
|
||||
## >>> Str.countClusters "👍"
|
||||
##
|
||||
## > The `countClusters` function traverses the entire string to calculate its answer,
|
||||
## > so it's much better for performance to use `Str.isEmpty` instead of
|
||||
## > calling `Str.countClusters` and checking whether the count was `0`.
|
||||
## > The `countClusters` function walks through the entire string to get its answer,
|
||||
## > so if you want to check whether a string is empty, you'll get much better performance
|
||||
## > 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
|
||||
##
|
||||
|
@ -47,7 +87,8 @@ api Str provides Str, isEmpty, join
|
|||
## [encoding](https://en.wikipedia.org/wiki/Character_encoding). As it happens,
|
||||
## 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
|
||||
## 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)
|
||||
## 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
|
||||
## 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,
|
||||
## but it's recommended to pass much smaller numbers instead.
|
||||
##
|
||||
## 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
|
||||
## If you want to keep all the digits, passing the same float to #Str.num
|
||||
## will do that.
|
||||
decimal : Float *, Ulen -> Str
|
||||
|
||||
## 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
|
||||
## 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
|
||||
|
||||
## Check
|
||||
|
||||
## Returns #True if the string is empty, and #False otherwise.
|
||||
##
|
||||
## >>> Str.isEmpty "hi!"
|
||||
##
|
||||
## >>> Str.isEmpty ""
|
||||
isEmpty : Str -> Bool
|
||||
|
||||
startsWith : Str, Str -> Bool
|
||||
|
@ -94,9 +135,9 @@ endsWith : 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
|
||||
|
||||
|
@ -111,14 +152,33 @@ join : List Str -> Str
|
|||
## >>> Str.joinWith [ "one", "two", "three" ] ", "
|
||||
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
|
||||
|
||||
padEnd : Str, Int, Str -> Str
|
||||
## Add to the end 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 "👩👩👦👦👩👩👦👦👩👩👦👦"
|
||||
padClustersEnd : Str, Int, Str -> Str
|
||||
|
||||
## Grapheme Clusters
|
||||
|
||||
## Split a string into its grapheme clusters.
|
||||
## Split a string into its individual grapheme clusters.
|
||||
##
|
||||
## >>> Str.clusters "1,2,3"
|
||||
##
|
||||
|
@ -126,6 +186,11 @@ padEnd : Str, Int, Str -> 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
|
||||
|
||||
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.
|
||||
foldRevUtf32 : Str, { start: state, step: (state, U32 -> state) } -> state
|
||||
|
||||
|
||||
|
||||
|
|
51
compiler/gen/src/layout_id.rs
Normal file
51
compiler/gen/src/layout_id.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -11,4 +11,5 @@
|
|||
// re-enable this when working on performance optimizations than have it block PRs.
|
||||
#![allow(clippy::large_enum_variant)]
|
||||
|
||||
pub mod layout_id;
|
||||
pub mod llvm;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::layout_id::LayoutIds;
|
||||
use crate::llvm::convert::{
|
||||
basic_type_from_layout, collection, get_fn_type, get_ptr_type, ptr_int,
|
||||
};
|
||||
|
@ -15,7 +16,7 @@ use inkwell::AddressSpace;
|
|||
use inkwell::{FloatPredicate, IntPredicate, OptimizationLevel};
|
||||
use roc_collections::all::ImMap;
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_mono::expr::{Expr, Proc, Procs};
|
||||
use roc_mono::expr::{Expr, Proc};
|
||||
use roc_mono::layout::{Builtin, Layout};
|
||||
use target_lexicon::CallingConvention;
|
||||
|
||||
|
@ -144,10 +145,10 @@ pub fn add_passes(fpm: &PassManager<FunctionValue<'_>>, opt_level: OptLevel) {
|
|||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn build_expr<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
expr: &'a Expr<'a>,
|
||||
procs: &Procs<'a>,
|
||||
expr: &Expr<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
use roc_mono::expr::Expr::*;
|
||||
|
||||
|
@ -166,14 +167,53 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
|||
let pass = env.arena.alloc(Expr::Store(pass_stores, pass_expr));
|
||||
let fail = env.arena.alloc(Expr::Store(fail_stores, fail_expr));
|
||||
|
||||
let conditional = Branch2 {
|
||||
cond: branch_symbol,
|
||||
pass,
|
||||
fail,
|
||||
ret_layout: ret_layout.clone(),
|
||||
};
|
||||
let ret_type =
|
||||
basic_type_from_layout(env.arena, env.context, &ret_layout, env.ptr_bytes);
|
||||
|
||||
build_branch2(env, scope, parent, conditional, procs)
|
||||
let cond_expr = load_symbol(env, scope, branch_symbol);
|
||||
|
||||
match cond_expr {
|
||||
IntValue(value) => {
|
||||
// This is a call tobuild_basic_phi2, except inlined to prevent
|
||||
// problems with lifetimes and closures involving layout_ids.
|
||||
let builder = env.builder;
|
||||
let context = env.context;
|
||||
|
||||
// build blocks
|
||||
let then_block = context.append_basic_block(parent, "then");
|
||||
let else_block = context.append_basic_block(parent, "else");
|
||||
let cont_block = context.append_basic_block(parent, "branchcont");
|
||||
|
||||
builder.build_conditional_branch(value, then_block, else_block);
|
||||
|
||||
// build then block
|
||||
builder.position_at_end(then_block);
|
||||
let then_val = build_expr(env, layout_ids, scope, parent, pass);
|
||||
builder.build_unconditional_branch(cont_block);
|
||||
|
||||
let then_block = builder.get_insert_block().unwrap();
|
||||
|
||||
// build else block
|
||||
builder.position_at_end(else_block);
|
||||
let else_val = build_expr(env, layout_ids, scope, parent, fail);
|
||||
builder.build_unconditional_branch(cont_block);
|
||||
|
||||
let else_block = builder.get_insert_block().unwrap();
|
||||
|
||||
// emit merge block
|
||||
builder.position_at_end(cont_block);
|
||||
|
||||
let phi = builder.build_phi(ret_type, "branch");
|
||||
|
||||
phi.add_incoming(&[(&then_val, then_block), (&else_val, else_block)]);
|
||||
|
||||
phi.as_basic_value()
|
||||
}
|
||||
_ => panic!(
|
||||
"Tried to make a branch out of an invalid condition: cond_expr = {:?}",
|
||||
cond_expr,
|
||||
),
|
||||
}
|
||||
}
|
||||
Switch {
|
||||
cond,
|
||||
|
@ -201,14 +241,14 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
|||
ret_type,
|
||||
};
|
||||
|
||||
build_switch(env, scope, parent, switch_args, procs)
|
||||
build_switch(env, layout_ids, scope, parent, switch_args)
|
||||
}
|
||||
Store(stores, ret) => {
|
||||
let mut scope = im_rc::HashMap::clone(scope);
|
||||
let context = &env.context;
|
||||
|
||||
for (symbol, layout, expr) in stores.iter() {
|
||||
let val = build_expr(env, &scope, parent, &expr, procs);
|
||||
let val = build_expr(env, layout_ids, &scope, parent, &expr);
|
||||
let expr_bt = basic_type_from_layout(env.arena, context, &layout, env.ptr_bytes);
|
||||
let alloca = create_entry_block_alloca(
|
||||
env,
|
||||
|
@ -229,16 +269,17 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
|||
scope.insert(*symbol, (layout.clone(), alloca));
|
||||
}
|
||||
|
||||
build_expr(env, &scope, parent, ret, procs)
|
||||
build_expr(env, layout_ids, &scope, parent, ret)
|
||||
}
|
||||
CallByName(symbol, args) => match *symbol {
|
||||
CallByName { name, layout, args } => match *name {
|
||||
Symbol::BOOL_OR => {
|
||||
// The (||) operator
|
||||
debug_assert!(args.len() == 2);
|
||||
|
||||
let comparison = build_expr(env, scope, parent, &args[0].0, procs).into_int_value();
|
||||
let comparison =
|
||||
build_expr(env, layout_ids, scope, parent, &args[0].0).into_int_value();
|
||||
let build_then = || env.context.bool_type().const_int(true as u64, false).into();
|
||||
let build_else = || build_expr(env, scope, parent, &args[1].0, procs);
|
||||
let build_else = || build_expr(env, layout_ids, scope, parent, &args[1].0);
|
||||
|
||||
let ret_type = env.context.bool_type().into();
|
||||
|
||||
|
@ -248,8 +289,9 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
|||
// The (&&) operator
|
||||
debug_assert!(args.len() == 2);
|
||||
|
||||
let comparison = build_expr(env, scope, parent, &args[0].0, procs).into_int_value();
|
||||
let build_then = || build_expr(env, scope, parent, &args[1].0, procs);
|
||||
let comparison =
|
||||
build_expr(env, layout_ids, scope, parent, &args[0].0).into_int_value();
|
||||
let build_then = || build_expr(env, layout_ids, scope, parent, &args[1].0);
|
||||
let build_else = || {
|
||||
env.context
|
||||
.bool_type()
|
||||
|
@ -265,7 +307,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
|||
// The (!) operator
|
||||
debug_assert!(args.len() == 1);
|
||||
|
||||
let arg = build_expr(env, scope, parent, &args[0].0, procs);
|
||||
let arg = build_expr(env, layout_ids, scope, parent, &args[0].0);
|
||||
|
||||
let int_val = env.builder.build_not(arg.into_int_value(), "bool_not");
|
||||
|
||||
|
@ -275,17 +317,27 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
|||
let mut arg_tuples: Vec<(BasicValueEnum, &'a Layout<'a>)> =
|
||||
Vec::with_capacity_in(args.len(), env.arena);
|
||||
|
||||
for (arg, layout) in args.iter() {
|
||||
arg_tuples.push((build_expr(env, scope, parent, arg, procs), layout));
|
||||
for (arg, arg_layout) in args.iter() {
|
||||
arg_tuples.push((build_expr(env, layout_ids, scope, parent, arg), arg_layout));
|
||||
}
|
||||
|
||||
call_with_args(*symbol, parent, arg_tuples.into_bump_slice(), env)
|
||||
call_with_args(
|
||||
env,
|
||||
layout_ids,
|
||||
layout,
|
||||
*name,
|
||||
parent,
|
||||
arg_tuples.into_bump_slice(),
|
||||
)
|
||||
}
|
||||
},
|
||||
FunctionPointer(symbol) => {
|
||||
FunctionPointer(symbol, layout) => {
|
||||
let fn_name = layout_ids
|
||||
.get(*symbol, layout)
|
||||
.to_symbol_string(*symbol, &env.interns);
|
||||
let ptr = env
|
||||
.module
|
||||
.get_function(symbol.ident_string(&env.interns))
|
||||
.get_function(fn_name.as_str())
|
||||
.unwrap_or_else(|| panic!("Could not get pointer to unknown function {:?}", symbol))
|
||||
.as_global_value()
|
||||
.as_pointer_value();
|
||||
|
@ -296,10 +348,10 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
|||
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), env.arena);
|
||||
|
||||
for arg in args.iter() {
|
||||
arg_vals.push(build_expr(env, scope, parent, arg, procs));
|
||||
arg_vals.push(build_expr(env, layout_ids, scope, parent, arg));
|
||||
}
|
||||
|
||||
let call = match build_expr(env, scope, parent, sub_expr, procs) {
|
||||
let call = match build_expr(env, layout_ids, scope, parent, sub_expr) {
|
||||
BasicValueEnum::PointerValue(ptr) => {
|
||||
env.builder.build_call(ptr, arg_vals.as_slice(), "tmp")
|
||||
}
|
||||
|
@ -393,7 +445,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
|||
let index_val = ctx.i32_type().const_int(index as u64, false);
|
||||
let elem_ptr =
|
||||
unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") };
|
||||
let val = build_expr(env, &scope, parent, &elem, procs);
|
||||
let val = build_expr(env, layout_ids, &scope, parent, &elem);
|
||||
|
||||
builder.build_store(elem_ptr, val);
|
||||
}
|
||||
|
@ -439,7 +491,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
|||
let mut field_vals = Vec::with_capacity_in(num_fields, env.arena);
|
||||
|
||||
for (field_expr, field_layout) in sorted_fields.iter() {
|
||||
let val = build_expr(env, &scope, parent, field_expr, procs);
|
||||
let val = build_expr(env, layout_ids, &scope, parent, field_expr);
|
||||
let field_type =
|
||||
basic_type_from_layout(env.arena, env.context, &field_layout, env.ptr_bytes);
|
||||
|
||||
|
@ -476,7 +528,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
|||
let mut field_vals = Vec::with_capacity_in(num_fields, env.arena);
|
||||
|
||||
for (field_expr, field_layout) in it {
|
||||
let val = build_expr(env, &scope, parent, field_expr, procs);
|
||||
let val = build_expr(env, layout_ids, &scope, parent, field_expr);
|
||||
let field_type =
|
||||
basic_type_from_layout(env.arena, env.context, &field_layout, env.ptr_bytes);
|
||||
|
||||
|
@ -516,7 +568,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
|||
let mut field_vals = Vec::with_capacity_in(num_fields, env.arena);
|
||||
|
||||
for (field_expr, field_layout) in arguments.iter() {
|
||||
let val = build_expr(env, &scope, parent, field_expr, procs);
|
||||
let val = build_expr(env, layout_ids, &scope, parent, field_expr);
|
||||
let field_type =
|
||||
basic_type_from_layout(env.arena, env.context, &field_layout, ptr_size);
|
||||
|
||||
|
@ -594,7 +646,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
|||
|
||||
// Get Struct val
|
||||
// Since this is a one-element tag union, we get the correct struct immediately
|
||||
let argument = build_expr(env, &scope, parent, expr, procs).into_struct_value();
|
||||
let argument = build_expr(env, layout_ids, &scope, parent, expr).into_struct_value();
|
||||
|
||||
builder
|
||||
.build_extract_value(
|
||||
|
@ -630,7 +682,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
|
|||
.struct_type(field_types.into_bump_slice(), false);
|
||||
|
||||
// cast the argument bytes into the desired shape for this tag
|
||||
let argument = build_expr(env, &scope, parent, expr, procs).into_struct_value();
|
||||
let argument = build_expr(env, layout_ids, &scope, parent, expr).into_struct_value();
|
||||
|
||||
let struct_value = cast_struct_struct(builder, argument, struct_type);
|
||||
|
||||
|
@ -705,41 +757,6 @@ fn extract_tag_discriminant<'a, 'ctx, 'env>(
|
|||
.into_int_value()
|
||||
}
|
||||
|
||||
struct Branch2<'a> {
|
||||
cond: &'a Symbol,
|
||||
pass: &'a Expr<'a>,
|
||||
fail: &'a Expr<'a>,
|
||||
ret_layout: Layout<'a>,
|
||||
}
|
||||
|
||||
fn build_branch2<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
cond: Branch2<'a>,
|
||||
procs: &Procs<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let ret_layout = cond.ret_layout;
|
||||
let pass = cond.pass;
|
||||
let fail = cond.fail;
|
||||
let ret_type = basic_type_from_layout(env.arena, env.context, &ret_layout, env.ptr_bytes);
|
||||
|
||||
let cond_expr = load_symbol(env, scope, cond.cond);
|
||||
|
||||
match cond_expr {
|
||||
IntValue(value) => {
|
||||
let build_then = || build_expr(env, scope, parent, pass, procs);
|
||||
let build_else = || build_expr(env, scope, parent, fail, procs);
|
||||
|
||||
build_basic_phi2(env, parent, value, build_then, build_else, ret_type)
|
||||
}
|
||||
_ => panic!(
|
||||
"Tried to make a branch out of an invalid condition: cond_expr = {:?}",
|
||||
cond_expr,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
struct SwitchArgs<'a, 'ctx> {
|
||||
pub cond_expr: &'a Expr<'a>,
|
||||
pub cond_layout: Layout<'a>,
|
||||
|
@ -750,10 +767,10 @@ struct SwitchArgs<'a, 'ctx> {
|
|||
|
||||
fn build_switch<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
switch_args: SwitchArgs<'a, 'ctx>,
|
||||
procs: &Procs<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let arena = env.arena;
|
||||
let builder = env.builder;
|
||||
|
@ -774,7 +791,7 @@ fn build_switch<'a, 'ctx, 'env>(
|
|||
Layout::Builtin(Builtin::Float64) => {
|
||||
// float matches are done on the bit pattern
|
||||
cond_layout = Layout::Builtin(Builtin::Int64);
|
||||
let full_cond = build_expr(env, scope, parent, cond_expr, procs);
|
||||
let full_cond = build_expr(env, layout_ids, scope, parent, cond_expr);
|
||||
|
||||
builder
|
||||
.build_bitcast(full_cond, env.context.i64_type(), "")
|
||||
|
@ -783,11 +800,14 @@ fn build_switch<'a, 'ctx, 'env>(
|
|||
Layout::Union(_) => {
|
||||
// we match on the discriminant, not the whole Tag
|
||||
cond_layout = Layout::Builtin(Builtin::Int64);
|
||||
let full_cond = build_expr(env, scope, parent, cond_expr, procs).into_struct_value();
|
||||
let full_cond =
|
||||
build_expr(env, layout_ids, scope, parent, cond_expr).into_struct_value();
|
||||
|
||||
extract_tag_discriminant(env, full_cond)
|
||||
}
|
||||
Layout::Builtin(_) => build_expr(env, scope, parent, cond_expr, procs).into_int_value(),
|
||||
Layout::Builtin(_) => {
|
||||
build_expr(env, layout_ids, scope, parent, cond_expr).into_int_value()
|
||||
}
|
||||
other => todo!("Build switch value from layout: {:?}", other),
|
||||
};
|
||||
|
||||
|
@ -824,7 +844,7 @@ fn build_switch<'a, 'ctx, 'env>(
|
|||
for ((_, branch_expr), (_, block)) in branches.iter().zip(cases) {
|
||||
builder.position_at_end(block);
|
||||
|
||||
let branch_val = build_expr(env, scope, parent, branch_expr, procs);
|
||||
let branch_val = build_expr(env, layout_ids, scope, parent, branch_expr);
|
||||
|
||||
builder.build_unconditional_branch(cont_block);
|
||||
|
||||
|
@ -834,7 +854,7 @@ fn build_switch<'a, 'ctx, 'env>(
|
|||
// The block for the conditional's default branch.
|
||||
builder.position_at_end(default_block);
|
||||
|
||||
let default_val = build_expr(env, scope, parent, default_branch, procs);
|
||||
let default_val = build_expr(env, layout_ids, scope, parent, default_branch);
|
||||
|
||||
builder.build_unconditional_branch(cont_block);
|
||||
|
||||
|
@ -852,40 +872,17 @@ fn build_switch<'a, 'ctx, 'env>(
|
|||
phi.as_basic_value()
|
||||
}
|
||||
|
||||
// TODO trim down these arguments
|
||||
#[allow(dead_code)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn build_expr_phi2<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
comparison: IntValue<'ctx>,
|
||||
pass: &'a Expr<'a>,
|
||||
fail: &'a Expr<'a>,
|
||||
ret_type: BasicTypeEnum<'ctx>,
|
||||
procs: &Procs<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
build_basic_phi2(
|
||||
env,
|
||||
parent,
|
||||
comparison,
|
||||
|| build_expr(env, scope, parent, pass, procs),
|
||||
|| build_expr(env, scope, parent, fail, procs),
|
||||
ret_type,
|
||||
)
|
||||
}
|
||||
|
||||
fn build_basic_phi2<'a, 'ctx, 'env, PassFn, FailFn>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
comparison: IntValue<'ctx>,
|
||||
build_pass: PassFn,
|
||||
build_fail: FailFn,
|
||||
mut build_pass: PassFn,
|
||||
mut build_fail: FailFn,
|
||||
ret_type: BasicTypeEnum<'ctx>,
|
||||
) -> BasicValueEnum<'ctx>
|
||||
where
|
||||
PassFn: Fn() -> BasicValueEnum<'ctx>,
|
||||
FailFn: Fn() -> BasicValueEnum<'ctx>,
|
||||
PassFn: FnMut() -> BasicValueEnum<'ctx>,
|
||||
FailFn: FnMut() -> BasicValueEnum<'ctx>,
|
||||
{
|
||||
let builder = env.builder;
|
||||
let context = env.context;
|
||||
|
@ -953,7 +950,9 @@ pub fn create_entry_block_alloca<'a, 'ctx>(
|
|||
|
||||
pub fn build_proc_header<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
symbol: Symbol,
|
||||
layout: &Layout<'a>,
|
||||
proc: &Proc<'a>,
|
||||
) -> (FunctionValue<'ctx>, Vec<'a, BasicTypeEnum<'ctx>>) {
|
||||
let args = proc.args;
|
||||
|
@ -972,11 +971,12 @@ pub fn build_proc_header<'a, 'ctx, 'env>(
|
|||
|
||||
let fn_type = get_fn_type(&ret_type, &arg_basic_types);
|
||||
|
||||
let fn_val = env.module.add_function(
|
||||
symbol.ident_string(&env.interns),
|
||||
fn_type,
|
||||
Some(Linkage::Private),
|
||||
);
|
||||
let fn_name = layout_ids
|
||||
.get(symbol, layout)
|
||||
.to_symbol_string(symbol, &env.interns);
|
||||
let fn_val = env
|
||||
.module
|
||||
.add_function(fn_name.as_str(), fn_type, Some(Linkage::Private));
|
||||
|
||||
fn_val.set_call_conventions(fn_val.get_call_conventions());
|
||||
|
||||
|
@ -985,8 +985,8 @@ pub fn build_proc_header<'a, 'ctx, 'env>(
|
|||
|
||||
pub fn build_proc<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
proc: Proc<'a>,
|
||||
procs: &Procs<'a>,
|
||||
fn_val: FunctionValue<'ctx>,
|
||||
arg_basic_types: Vec<'a, BasicTypeEnum<'ctx>>,
|
||||
) {
|
||||
|
@ -1015,7 +1015,7 @@ pub fn build_proc<'a, 'ctx, 'env>(
|
|||
scope.insert(*arg_symbol, (layout.clone(), alloca));
|
||||
}
|
||||
|
||||
let body = build_expr(env, &scope, fn_val, &proc.body, procs);
|
||||
let body = build_expr(env, layout_ids, &scope, fn_val, &proc.body);
|
||||
|
||||
builder.build_return(Some(&body));
|
||||
}
|
||||
|
@ -1033,10 +1033,12 @@ pub fn verify_fn(fn_val: FunctionValue<'_>) {
|
|||
#[inline(always)]
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn call_with_args<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
layout: &Layout<'a>,
|
||||
symbol: Symbol,
|
||||
parent: FunctionValue<'ctx>,
|
||||
args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)],
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
match symbol {
|
||||
Symbol::INT_ADD | Symbol::NUM_ADD => {
|
||||
|
@ -1396,9 +1398,12 @@ fn call_with_args<'a, 'ctx, 'env>(
|
|||
BasicValueEnum::IntValue(int_val)
|
||||
}
|
||||
_ => {
|
||||
let fn_name = layout_ids
|
||||
.get(symbol, layout)
|
||||
.to_symbol_string(symbol, &env.interns);
|
||||
let fn_val = env
|
||||
.module
|
||||
.get_function(symbol.ident_string(&env.interns))
|
||||
.get_function(fn_name.as_str())
|
||||
.unwrap_or_else(|| panic!("Unrecognized function: {:?}", symbol));
|
||||
|
||||
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), env.arena);
|
||||
|
|
|
@ -38,7 +38,7 @@ macro_rules! assert_llvm_evals_to {
|
|||
fpm.initialize();
|
||||
|
||||
// Compute main_fn_type before moving subs to Env
|
||||
let layout = Layout::from_content(&arena, content, &subs, ptr_bytes)
|
||||
let layout = Layout::new(&arena, content, &subs, ptr_bytes)
|
||||
.unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs));
|
||||
let execution_engine =
|
||||
module
|
||||
|
@ -60,6 +60,7 @@ macro_rules! assert_llvm_evals_to {
|
|||
};
|
||||
let mut procs = Procs::default();
|
||||
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
|
||||
let mut layout_ids = roc_gen::layout_id::LayoutIds::default();
|
||||
|
||||
// Populate Procs and get the low-level Expr from the canonical Expr
|
||||
let mut mono_problems = Vec::new();
|
||||
|
@ -78,13 +79,16 @@ macro_rules! assert_llvm_evals_to {
|
|||
env.interns.all_ident_ids.insert(home, ident_ids);
|
||||
|
||||
let mut headers = Vec::with_capacity(procs.len());
|
||||
let (mut proc_map, runtime_errors) = procs.into_map();
|
||||
|
||||
assert_eq!(runtime_errors, roc_collections::all::MutSet::default());
|
||||
|
||||
// Add all the Proc headers to the module.
|
||||
// We have to do this in a separate pass first,
|
||||
// because their bodies may reference each other.
|
||||
for (symbol, opt_proc) in procs.as_map().into_iter() {
|
||||
if let Some(proc) = opt_proc {
|
||||
let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc);
|
||||
for (symbol, mut procs_by_layout) in proc_map.drain() {
|
||||
for (layout, proc) in procs_by_layout.drain() {
|
||||
let (fn_val, arg_basic_types) = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
|
||||
|
||||
headers.push((proc, fn_val, arg_basic_types));
|
||||
}
|
||||
|
@ -96,7 +100,7 @@ macro_rules! assert_llvm_evals_to {
|
|||
// (This approach means we don't have to defensively clone name here.)
|
||||
//
|
||||
// println!("\n\nBuilding and then verifying function {}\n\n", name);
|
||||
build_proc(&env, proc, &procs, fn_val, arg_basic_types);
|
||||
build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types);
|
||||
|
||||
if fn_val.verify(true) {
|
||||
fpm.run_on(&fn_val);
|
||||
|
@ -119,10 +123,10 @@ macro_rules! assert_llvm_evals_to {
|
|||
|
||||
let ret = roc_gen::llvm::build::build_expr(
|
||||
&env,
|
||||
&mut layout_ids,
|
||||
&ImMap::default(),
|
||||
main_fn,
|
||||
&main_body,
|
||||
&mut Procs::default(),
|
||||
);
|
||||
|
||||
builder.build_return(Some(&ret));
|
||||
|
@ -198,7 +202,7 @@ macro_rules! assert_opt_evals_to {
|
|||
fpm.initialize();
|
||||
|
||||
// Compute main_fn_type before moving subs to Env
|
||||
let layout = Layout::from_content(&arena, content, &subs, ptr_bytes)
|
||||
let layout = Layout::new(&arena, content, &subs, ptr_bytes)
|
||||
.unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs));
|
||||
|
||||
let execution_engine =
|
||||
|
@ -221,6 +225,7 @@ macro_rules! assert_opt_evals_to {
|
|||
};
|
||||
let mut procs = Procs::default();
|
||||
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
|
||||
let mut layout_ids = roc_gen::layout_id::LayoutIds::default();
|
||||
|
||||
// Populate Procs and get the low-level Expr from the canonical Expr
|
||||
let mut mono_problems = Vec::new();
|
||||
|
@ -239,13 +244,16 @@ macro_rules! assert_opt_evals_to {
|
|||
env.interns.all_ident_ids.insert(home, ident_ids);
|
||||
|
||||
let mut headers = Vec::with_capacity(procs.len());
|
||||
let (mut proc_map, runtime_errors) = procs.into_map();
|
||||
|
||||
assert_eq!(runtime_errors, roc_collections::all::MutSet::default());
|
||||
|
||||
// Add all the Proc headers to the module.
|
||||
// We have to do this in a separate pass first,
|
||||
// because their bodies may reference each other.
|
||||
for (symbol, opt_proc) in procs.as_map().into_iter() {
|
||||
if let Some(proc) = opt_proc {
|
||||
let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc);
|
||||
for (symbol, mut procs_by_layout) in proc_map.drain() {
|
||||
for (layout, proc) in procs_by_layout.drain() {
|
||||
let (fn_val, arg_basic_types) = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
|
||||
|
||||
headers.push((proc, fn_val, arg_basic_types));
|
||||
}
|
||||
|
@ -257,7 +265,7 @@ macro_rules! assert_opt_evals_to {
|
|||
// (This approach means we don't have to defensively clone name here.)
|
||||
//
|
||||
// println!("\n\nBuilding and then verifying function {}\n\n", name);
|
||||
build_proc(&env, proc, &procs, fn_val, arg_basic_types);
|
||||
build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types);
|
||||
|
||||
if fn_val.verify(true) {
|
||||
fpm.run_on(&fn_val);
|
||||
|
@ -280,10 +288,10 @@ macro_rules! assert_opt_evals_to {
|
|||
|
||||
let ret = roc_gen::llvm::build::build_expr(
|
||||
&env,
|
||||
&mut layout_ids,
|
||||
&ImMap::default(),
|
||||
main_fn,
|
||||
&main_body,
|
||||
&mut Procs::default(),
|
||||
);
|
||||
|
||||
builder.build_return(Some(&ret));
|
||||
|
@ -354,7 +362,7 @@ macro_rules! emit_expr {
|
|||
fpm.initialize();
|
||||
|
||||
// Compute main_fn_type before moving subs to Env
|
||||
let layout = Layout::from_content(&arena, content, &subs, ptr_bytes)
|
||||
let layout = Layout::new(&arena, content, &subs, ptr_bytes)
|
||||
.unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs));
|
||||
|
||||
let execution_engine =
|
||||
|
|
|
@ -48,6 +48,10 @@ impl Symbol {
|
|||
IdentId((self.0 >> 32) as u32)
|
||||
}
|
||||
|
||||
pub fn is_builtin(self) -> bool {
|
||||
self.module_id().is_builtin()
|
||||
}
|
||||
|
||||
pub fn module_string<'a>(&self, interns: &'a Interns) -> &'a InlinableString {
|
||||
interns
|
||||
.module_ids
|
||||
|
|
|
@ -1203,14 +1203,16 @@ fn boolean_all<'a>(arena: &'a Bump, tests: Vec<(Expr<'a>, Expr<'a>, Layout<'a>)>
|
|||
let mut expr = Expr::Bool(true);
|
||||
|
||||
for (lhs, rhs, layout) in tests.into_iter().rev() {
|
||||
let test = specialize_equality(arena, lhs, rhs, layout);
|
||||
expr = Expr::CallByName(
|
||||
Symbol::BOOL_AND,
|
||||
arena.alloc([
|
||||
let test = specialize_equality(arena, lhs, rhs, layout.clone());
|
||||
|
||||
expr = Expr::CallByName {
|
||||
name: Symbol::BOOL_AND,
|
||||
layout,
|
||||
args: arena.alloc([
|
||||
(test, Layout::Builtin(Builtin::Bool)),
|
||||
(expr, Layout::Builtin(Builtin::Bool)),
|
||||
]),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
expr
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use crate::layout::{Builtin, Layout};
|
||||
use crate::layout::{Builtin, Layout, LayoutCache};
|
||||
use crate::pattern::{Ctor, Guard, RenderAs, TagId};
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_collections::all::{default_hasher, MutMap, MutSet};
|
||||
use roc_module::ident::{Ident, Lowercase, TagName};
|
||||
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
|
||||
use roc_region::all::{Located, Region};
|
||||
use roc_types::subs::{Content, ContentHash, FlatType, Subs, Variable};
|
||||
use roc_types::subs::{Content, FlatType, Subs, Variable};
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -29,8 +30,9 @@ pub struct Proc<'a> {
|
|||
pub struct Procs<'a> {
|
||||
pub user_defined: MutMap<Symbol, PartialProc<'a>>,
|
||||
pub module_thunks: MutSet<Symbol>,
|
||||
anonymous: MutMap<Symbol, Option<Proc<'a>>>,
|
||||
specializations: MutMap<ContentHash, (Symbol, Option<Proc<'a>>)>,
|
||||
runtime_errors: MutSet<Symbol>,
|
||||
specializations: MutMap<Symbol, MutMap<Layout<'a>, Proc<'a>>>,
|
||||
pending_specializations: MutMap<Symbol, Layout<'a>>,
|
||||
builtin: MutSet<Symbol>,
|
||||
}
|
||||
|
||||
|
@ -57,6 +59,8 @@ impl<'a> Procs<'a> {
|
|||
);
|
||||
}
|
||||
|
||||
// TODO trim these down
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn insert_anonymous(
|
||||
&mut self,
|
||||
env: &mut Env<'a, '_>,
|
||||
|
@ -65,13 +69,14 @@ impl<'a> Procs<'a> {
|
|||
loc_args: std::vec::Vec<(Variable, Located<roc_can::pattern::Pattern>)>,
|
||||
loc_body: Located<roc_can::expr::Expr>,
|
||||
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);
|
||||
|
||||
// an anonymous closure. These will always be specialized already
|
||||
// by the surrounding context
|
||||
|
||||
let opt_proc = specialize_proc_body(
|
||||
match specialize_proc_body(
|
||||
env,
|
||||
self,
|
||||
annotation,
|
||||
|
@ -81,19 +86,38 @@ impl<'a> Procs<'a> {
|
|||
&arg_symbols,
|
||||
annotation,
|
||||
body.value,
|
||||
)
|
||||
.ok();
|
||||
layout_cache,
|
||||
) {
|
||||
Ok(proc) => {
|
||||
let layout = layout_cache
|
||||
.from_var(env.arena, annotation, env.subs, env.pointer_size)
|
||||
.unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err));
|
||||
|
||||
self.anonymous.insert(symbol, opt_proc);
|
||||
self.insert_specialization(symbol, layout.clone(), proc);
|
||||
|
||||
Ok(layout)
|
||||
}
|
||||
Err(()) => {
|
||||
self.runtime_errors.insert(symbol);
|
||||
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_specialization(
|
||||
&mut self,
|
||||
hash: ContentHash,
|
||||
spec_name: Symbol,
|
||||
proc: Option<Proc<'a>>,
|
||||
) {
|
||||
self.specializations.insert(hash, (spec_name, proc));
|
||||
fn insert_specialization(&mut self, symbol: Symbol, layout: Layout<'a>, proc: Proc<'a>) {
|
||||
let procs_by_layout = self
|
||||
.specializations
|
||||
.entry(symbol)
|
||||
.or_insert_with(|| HashMap::with_capacity_and_hasher(1, default_hasher()));
|
||||
|
||||
// If we already have an entry for this, it should be no different
|
||||
// from what we're about to insert.
|
||||
debug_assert!(
|
||||
!procs_by_layout.contains_key(&layout) || procs_by_layout.get(&layout) == Some(&proc)
|
||||
);
|
||||
|
||||
procs_by_layout.insert(layout, proc);
|
||||
}
|
||||
|
||||
fn get_user_defined(&self, symbol: Symbol) -> Option<&PartialProc<'a>> {
|
||||
|
@ -101,10 +125,10 @@ impl<'a> Procs<'a> {
|
|||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
let anonymous: usize = self.anonymous.len();
|
||||
let user_defined: usize = self.specializations.len();
|
||||
let runtime_errors: usize = self.runtime_errors.len();
|
||||
let specializations: usize = self.specializations.len();
|
||||
|
||||
anonymous + user_defined
|
||||
runtime_errors + specializations
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
|
@ -115,22 +139,20 @@ impl<'a> Procs<'a> {
|
|||
self.builtin.insert(symbol);
|
||||
}
|
||||
|
||||
pub fn as_map(&self) -> MutMap<Symbol, Option<Proc<'a>>> {
|
||||
let mut result = MutMap::default();
|
||||
|
||||
for (symbol, opt_proc) in self.specializations.values() {
|
||||
result.insert(*symbol, opt_proc.clone());
|
||||
}
|
||||
|
||||
for (symbol, proc) in self.anonymous.clone().into_iter() {
|
||||
result.insert(symbol, proc);
|
||||
}
|
||||
pub fn into_map(self) -> (MutMap<Symbol, MutMap<Layout<'a>, Proc<'a>>>, MutSet<Symbol>) {
|
||||
let mut specializations = self.specializations;
|
||||
|
||||
for symbol in self.builtin.iter() {
|
||||
result.insert(*symbol, None);
|
||||
// Builtins should only ever be stored as empty maps.
|
||||
debug_assert!(
|
||||
!specializations.contains_key(&symbol)
|
||||
|| specializations.get(&symbol).unwrap().is_empty()
|
||||
);
|
||||
|
||||
specializations.insert(*symbol, MutMap::default());
|
||||
}
|
||||
|
||||
result
|
||||
(specializations, self.runtime_errors)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -177,8 +199,13 @@ pub enum Expr<'a> {
|
|||
Store(&'a [(Symbol, Layout<'a>, Expr<'a>)], &'a Expr<'a>),
|
||||
|
||||
// Functions
|
||||
FunctionPointer(Symbol),
|
||||
CallByName(Symbol, &'a [(Expr<'a>, Layout<'a>)]),
|
||||
FunctionPointer(Symbol, Layout<'a>),
|
||||
RuntimeErrorFunction(&'a str),
|
||||
CallByName {
|
||||
name: Symbol,
|
||||
layout: Layout<'a>,
|
||||
args: &'a [(Expr<'a>, Layout<'a>)],
|
||||
},
|
||||
CallByPointer(&'a Expr<'a>, &'a [Expr<'a>], Layout<'a>),
|
||||
|
||||
// Exactly two conditional branches, e.g. if/else
|
||||
|
@ -248,7 +275,9 @@ impl<'a> Expr<'a> {
|
|||
can_expr: roc_can::expr::Expr,
|
||||
procs: &mut Procs<'a>,
|
||||
) -> Self {
|
||||
from_can(env, can_expr, procs, None)
|
||||
let mut layout_cache = LayoutCache::default();
|
||||
|
||||
from_can(env, can_expr, procs, &mut layout_cache)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -448,7 +477,7 @@ fn from_can<'a>(
|
|||
env: &mut Env<'a, '_>,
|
||||
can_expr: roc_can::expr::Expr,
|
||||
procs: &mut Procs<'a>,
|
||||
name: Option<Symbol>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
) -> Expr<'a> {
|
||||
use roc_can::expr::Expr::*;
|
||||
|
||||
|
@ -467,123 +496,51 @@ fn from_can<'a>(
|
|||
let ret_var = partial_proc.annotation;
|
||||
|
||||
// This is a top-level declaration, which will code gen to a 0-arity thunk.
|
||||
call_by_name(env, procs, fn_var, ret_var, symbol, std::vec::Vec::new())
|
||||
call_by_name(
|
||||
env,
|
||||
procs,
|
||||
fn_var,
|
||||
ret_var,
|
||||
symbol,
|
||||
std::vec::Vec::new(),
|
||||
layout_cache,
|
||||
)
|
||||
} else {
|
||||
Expr::Load(symbol)
|
||||
}
|
||||
}
|
||||
LetRec(defs, ret_expr, _, _) => from_can_defs(env, defs, *ret_expr, procs),
|
||||
LetNonRec(def, ret_expr, _, _) => from_can_defs(env, vec![*def], *ret_expr, procs),
|
||||
LetRec(defs, ret_expr, _, _) => from_can_defs(env, defs, *ret_expr, layout_cache, procs),
|
||||
LetNonRec(def, ret_expr, _, _) => {
|
||||
from_can_defs(env, vec![*def], *ret_expr, layout_cache, procs)
|
||||
}
|
||||
|
||||
Closure(ann, original_name, _, loc_args, boxed_body) => {
|
||||
Closure(ann, name, _, loc_args, boxed_body) => {
|
||||
let (loc_body, ret_var) = *boxed_body;
|
||||
let symbol = match name {
|
||||
Some(symbol) => {
|
||||
procs.insert_named(env, symbol, ann, loc_args, loc_body, ret_var);
|
||||
|
||||
symbol
|
||||
match procs.insert_anonymous(env, name, ann, loc_args, loc_body, ret_var, layout_cache)
|
||||
{
|
||||
Ok(layout) => Expr::FunctionPointer(name, layout),
|
||||
Err(()) => {
|
||||
// TODO make this message better
|
||||
Expr::RuntimeErrorFunction("This function threw a runtime error.")
|
||||
}
|
||||
None => {
|
||||
procs.insert_anonymous(env, original_name, ann, loc_args, loc_body, ret_var);
|
||||
|
||||
original_name
|
||||
}
|
||||
};
|
||||
|
||||
Expr::FunctionPointer(symbol)
|
||||
}
|
||||
}
|
||||
|
||||
Call(boxed, loc_args, _) => {
|
||||
use IntOrFloat::*;
|
||||
|
||||
let (fn_var, loc_expr, ret_var) = *boxed;
|
||||
|
||||
let specialize_builtin_functions = {
|
||||
|env: &mut Env<'a, '_>, symbol: Symbol| {
|
||||
if !symbol.module_id().is_builtin() {
|
||||
// return unchanged
|
||||
symbol
|
||||
} else {
|
||||
match symbol {
|
||||
Symbol::NUM_ADD => match num_to_int_or_float(env.subs, ret_var) {
|
||||
FloatType => Symbol::FLOAT_ADD,
|
||||
IntType => Symbol::INT_ADD,
|
||||
},
|
||||
Symbol::NUM_SUB => match num_to_int_or_float(env.subs, ret_var) {
|
||||
FloatType => Symbol::FLOAT_SUB,
|
||||
IntType => Symbol::INT_SUB,
|
||||
},
|
||||
Symbol::NUM_LTE => match num_to_int_or_float(env.subs, loc_args[0].0) {
|
||||
FloatType => Symbol::FLOAT_LTE,
|
||||
IntType => Symbol::INT_LTE,
|
||||
},
|
||||
Symbol::NUM_LT => match num_to_int_or_float(env.subs, loc_args[0].0) {
|
||||
FloatType => Symbol::FLOAT_LT,
|
||||
IntType => Symbol::INT_LT,
|
||||
},
|
||||
Symbol::NUM_GTE => match num_to_int_or_float(env.subs, loc_args[0].0) {
|
||||
FloatType => Symbol::FLOAT_GTE,
|
||||
IntType => Symbol::INT_GTE,
|
||||
},
|
||||
Symbol::NUM_GT => match num_to_int_or_float(env.subs, loc_args[0].0) {
|
||||
FloatType => Symbol::FLOAT_GT,
|
||||
IntType => Symbol::INT_GT,
|
||||
},
|
||||
// TODO make this work for more than just int/float
|
||||
Symbol::BOOL_EQ => {
|
||||
match Layout::from_var(
|
||||
env.arena,
|
||||
loc_args[0].0,
|
||||
env.subs,
|
||||
env.pointer_size,
|
||||
) {
|
||||
Ok(Layout::Builtin(builtin)) => match builtin {
|
||||
Builtin::Int64 => Symbol::INT_EQ_I64,
|
||||
Builtin::Float64 => Symbol::FLOAT_EQ,
|
||||
Builtin::Bool => Symbol::INT_EQ_I1,
|
||||
Builtin::Byte => Symbol::INT_EQ_I8,
|
||||
_ => panic!("Equality not implemented for {:?}", builtin),
|
||||
},
|
||||
Ok(complex) => panic!(
|
||||
"TODO support equality on complex layouts like {:?}",
|
||||
complex
|
||||
),
|
||||
Err(()) => panic!("Invalid layout"),
|
||||
}
|
||||
}
|
||||
Symbol::BOOL_NEQ => {
|
||||
match Layout::from_var(
|
||||
env.arena,
|
||||
loc_args[0].0,
|
||||
env.subs,
|
||||
env.pointer_size,
|
||||
) {
|
||||
Ok(Layout::Builtin(builtin)) => match builtin {
|
||||
Builtin::Int64 => Symbol::INT_NEQ_I64,
|
||||
Builtin::Bool => Symbol::INT_NEQ_I1,
|
||||
Builtin::Byte => Symbol::INT_NEQ_I8,
|
||||
_ => {
|
||||
panic!("Not-Equality not implemented for {:?}", builtin)
|
||||
}
|
||||
},
|
||||
Ok(complex) => panic!(
|
||||
"TODO support equality on complex layouts like {:?}",
|
||||
complex
|
||||
),
|
||||
Err(()) => panic!("Invalid layout"),
|
||||
}
|
||||
}
|
||||
_ => symbol,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match from_can(env, loc_expr.value, procs, None) {
|
||||
match from_can(env, loc_expr.value, procs, layout_cache) {
|
||||
Expr::Load(proc_name) => {
|
||||
// Some functions can potentially mutate in-place.
|
||||
// If we have one of those, switch to the in-place version if appropriate.
|
||||
match specialize_builtin_functions(env, proc_name) {
|
||||
match specialize_builtin_functions(
|
||||
env,
|
||||
proc_name,
|
||||
loc_args.as_slice(),
|
||||
ret_var,
|
||||
layout_cache,
|
||||
) {
|
||||
Symbol::LIST_SET => {
|
||||
let subs = &env.subs;
|
||||
// The first arg is the one with the List in it.
|
||||
|
@ -610,9 +567,25 @@ fn from_can<'a>(
|
|||
Symbol::LIST_SET
|
||||
};
|
||||
|
||||
call_by_name(env, procs, fn_var, ret_var, new_name, loc_args)
|
||||
call_by_name(
|
||||
env,
|
||||
procs,
|
||||
fn_var,
|
||||
ret_var,
|
||||
new_name,
|
||||
loc_args,
|
||||
layout_cache,
|
||||
)
|
||||
}
|
||||
_ => call_by_name(env, procs, fn_var, ret_var, proc_name, loc_args),
|
||||
_ => call_by_name(
|
||||
env,
|
||||
procs,
|
||||
fn_var,
|
||||
ret_var,
|
||||
proc_name,
|
||||
loc_args,
|
||||
layout_cache,
|
||||
),
|
||||
}
|
||||
}
|
||||
specialized_proc_symbol => call_by_name(
|
||||
|
@ -622,6 +595,7 @@ fn from_can<'a>(
|
|||
ret_var,
|
||||
specialized_proc_symbol,
|
||||
loc_args,
|
||||
layout_cache,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -640,10 +614,11 @@ fn from_can<'a>(
|
|||
let mut args = Vec::with_capacity_in(loc_args.len(), env.arena);
|
||||
|
||||
for (_, loc_arg) in loc_args {
|
||||
args.push(from_can(env, loc_arg.value, procs, None));
|
||||
args.push(from_can(env, loc_arg.value, procs, layout_cache));
|
||||
}
|
||||
|
||||
let layout = Layout::from_var(env.arena, fn_var, env.subs, env.pointer_size)
|
||||
let layout = layout_cache
|
||||
.from_var(env.arena, fn_var, env.subs, env.pointer_size)
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("TODO turn fn_var into a RuntimeError {:?}", err)
|
||||
});
|
||||
|
@ -658,7 +633,16 @@ fn from_can<'a>(
|
|||
region,
|
||||
loc_cond,
|
||||
branches,
|
||||
} => from_can_when(env, cond_var, expr_var, region, *loc_cond, branches, procs),
|
||||
} => from_can_when(
|
||||
env,
|
||||
cond_var,
|
||||
expr_var,
|
||||
region,
|
||||
*loc_cond,
|
||||
branches,
|
||||
layout_cache,
|
||||
procs,
|
||||
),
|
||||
|
||||
If {
|
||||
cond_var,
|
||||
|
@ -666,16 +650,18 @@ fn from_can<'a>(
|
|||
branches,
|
||||
final_else,
|
||||
} => {
|
||||
let mut expr = from_can(env, final_else.value, procs, None);
|
||||
let mut expr = from_can(env, final_else.value, procs, layout_cache);
|
||||
|
||||
let ret_layout = Layout::from_var(env.arena, branch_var, env.subs, env.pointer_size)
|
||||
let ret_layout = layout_cache
|
||||
.from_var(env.arena, branch_var, env.subs, env.pointer_size)
|
||||
.expect("invalid ret_layout");
|
||||
let cond_layout = Layout::from_var(env.arena, cond_var, env.subs, env.pointer_size)
|
||||
let cond_layout = layout_cache
|
||||
.from_var(env.arena, cond_var, env.subs, env.pointer_size)
|
||||
.expect("invalid cond_layout");
|
||||
|
||||
for (loc_cond, loc_then) in branches.into_iter().rev() {
|
||||
let cond = from_can(env, loc_cond.value, procs, None);
|
||||
let then = from_can(env, loc_then.value, procs, None);
|
||||
let cond = from_can(env, loc_cond.value, procs, layout_cache);
|
||||
let then = from_can(env, loc_then.value, procs, layout_cache);
|
||||
|
||||
let branch_symbol = env.unique_symbol();
|
||||
|
||||
|
@ -716,7 +702,7 @@ fn from_can<'a>(
|
|||
|
||||
for (label, layout) in btree {
|
||||
let field = fields.remove(&label).unwrap();
|
||||
let expr = from_can(env, field.loc_expr.value, procs, None);
|
||||
let expr = from_can(env, field.loc_expr.value, procs, layout_cache);
|
||||
|
||||
field_tuples.push((expr, layout));
|
||||
}
|
||||
|
@ -757,7 +743,7 @@ fn from_can<'a>(
|
|||
Unwrapped(field_layouts) => {
|
||||
let field_exprs = args
|
||||
.into_iter()
|
||||
.map(|(_, arg)| from_can(env, arg.value, procs, None));
|
||||
.map(|(_, arg)| from_can(env, arg.value, procs, layout_cache));
|
||||
|
||||
let mut field_tuples = Vec::with_capacity_in(field_layouts.len(), arena);
|
||||
|
||||
|
@ -779,7 +765,7 @@ fn from_can<'a>(
|
|||
|
||||
let it = std::iter::once(Expr::Int(tag_id as i64)).chain(
|
||||
args.into_iter()
|
||||
.map(|(_, arg)| from_can(env, arg.value, procs, None)),
|
||||
.map(|(_, arg)| from_can(env, arg.value, procs, layout_cache)),
|
||||
);
|
||||
|
||||
for (arg_layout, arg_expr) in argument_layouts.iter().zip(it) {
|
||||
|
@ -832,7 +818,7 @@ fn from_can<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
let record = arena.alloc(from_can(env, loc_expr.value, procs, None));
|
||||
let record = arena.alloc(from_can(env, loc_expr.value, procs, layout_cache));
|
||||
|
||||
Expr::AccessAtIndex {
|
||||
index: index.expect("field not in its own type") as u64,
|
||||
|
@ -853,8 +839,8 @@ fn from_can<'a>(
|
|||
// We have to special-case the empty list, because trying to
|
||||
// compute a layout for an unbound var won't work.
|
||||
Content::FlexVar(_) => Layout::Builtin(Builtin::EmptyList),
|
||||
content => match Layout::from_content(arena, content, env.subs, env.pointer_size) {
|
||||
Ok(layout) => layout,
|
||||
_ => match layout_cache.from_var(arena, elem_var, env.subs, env.pointer_size) {
|
||||
Ok(layout) => layout.clone(),
|
||||
Err(()) => {
|
||||
panic!("TODO gracefully handle List with invalid element layout");
|
||||
}
|
||||
|
@ -864,7 +850,7 @@ fn from_can<'a>(
|
|||
let mut elems = Vec::with_capacity_in(loc_elems.len(), arena);
|
||||
|
||||
for loc_elem in loc_elems {
|
||||
elems.push(from_can(env, loc_elem.value, procs, None));
|
||||
elems.push(from_can(env, loc_elem.value, procs, layout_cache));
|
||||
}
|
||||
|
||||
Expr::Array {
|
||||
|
@ -1022,6 +1008,7 @@ fn from_can_defs<'a>(
|
|||
env: &mut Env<'a, '_>,
|
||||
defs: std::vec::Vec<roc_can::def::Def>,
|
||||
ret_expr: Located<roc_can::expr::Expr>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
procs: &mut Procs<'a>,
|
||||
) -> Expr<'a> {
|
||||
use roc_can::expr::Expr::*;
|
||||
|
@ -1029,6 +1016,7 @@ fn from_can_defs<'a>(
|
|||
|
||||
let arena = env.arena;
|
||||
let mut stored = Vec::with_capacity_in(defs.len(), arena);
|
||||
|
||||
for def in defs {
|
||||
let loc_pattern = def.loc_pattern;
|
||||
let loc_expr = def.loc_expr;
|
||||
|
@ -1047,18 +1035,29 @@ fn from_can_defs<'a>(
|
|||
//
|
||||
if let Identifier(symbol) = &loc_pattern.value {
|
||||
if let Closure(_, _, _, _, _) = &loc_expr.value {
|
||||
// Extract Procs, but discard the resulting Expr::Load.
|
||||
// That Load looks up the pointer, which we won't use here!
|
||||
from_can(env, loc_expr.value, procs, Some(*symbol));
|
||||
// Now that we know for sure it's a closure, get an owned
|
||||
// version of these variant args so we can use them properly.
|
||||
match loc_expr.value {
|
||||
Closure(ann, _, _, loc_args, boxed_body) => {
|
||||
// Extract Procs, but discard the resulting Expr::Load.
|
||||
// That Load looks up the pointer, which we won't use here!
|
||||
|
||||
continue;
|
||||
let (loc_body, ret_var) = *boxed_body;
|
||||
|
||||
procs.insert_named(env, *symbol, ann, loc_args, loc_body, ret_var);
|
||||
|
||||
continue;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If it wasn't specifically an Identifier & Closure, proceed as normal.
|
||||
let mono_pattern = from_can_pattern(env, &loc_pattern.value);
|
||||
|
||||
let layout = Layout::from_var(env.arena, def.expr_var, env.subs, env.pointer_size)
|
||||
let layout = layout_cache
|
||||
.from_var(env.arena, def.expr_var, env.subs, env.pointer_size)
|
||||
.expect("invalid layout");
|
||||
|
||||
match &mono_pattern {
|
||||
|
@ -1066,7 +1065,7 @@ fn from_can_defs<'a>(
|
|||
stored.push((
|
||||
*symbol,
|
||||
layout.clone(),
|
||||
from_can(env, loc_expr.value, procs, None),
|
||||
from_can(env, loc_expr.value, procs, layout_cache),
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
|
@ -1093,7 +1092,7 @@ fn from_can_defs<'a>(
|
|||
stored.push((
|
||||
symbol,
|
||||
layout.clone(),
|
||||
from_can(env, loc_expr.value, procs, None),
|
||||
from_can(env, loc_expr.value, procs, layout_cache),
|
||||
));
|
||||
|
||||
match store_pattern(env, &mono_pattern, symbol, layout, &mut stored) {
|
||||
|
@ -1108,7 +1107,7 @@ fn from_can_defs<'a>(
|
|||
}
|
||||
// At this point, it's safe to assume we aren't assigning a Closure to a def.
|
||||
// Extract Procs from the def body and the ret expression, and return the result!
|
||||
let ret = from_can(env, ret_expr.value, procs, None);
|
||||
let ret = from_can(env, ret_expr.value, procs, layout_cache);
|
||||
|
||||
if stored.is_empty() {
|
||||
ret
|
||||
|
@ -1117,6 +1116,8 @@ fn from_can_defs<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
// TODO trim these down
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn from_can_when<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
cond_var: Variable,
|
||||
|
@ -1124,6 +1125,7 @@ fn from_can_when<'a>(
|
|||
region: Region,
|
||||
loc_cond: Located<roc_can::expr::Expr>,
|
||||
mut branches: std::vec::Vec<roc_can::expr::WhenBranch>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
procs: &mut Procs<'a>,
|
||||
) -> Expr<'a> {
|
||||
if branches.is_empty() {
|
||||
|
@ -1168,32 +1170,34 @@ fn from_can_when<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
let cond_layout = Layout::from_var(env.arena, cond_var, env.subs, env.pointer_size)
|
||||
let cond_layout = layout_cache
|
||||
.from_var(env.arena, cond_var, env.subs, env.pointer_size)
|
||||
.unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err));
|
||||
let cond_symbol = env.unique_symbol();
|
||||
let cond = from_can(env, loc_cond.value, procs, None);
|
||||
let cond = from_can(env, loc_cond.value, procs, layout_cache);
|
||||
stored.push((cond_symbol, cond_layout.clone(), cond));
|
||||
|
||||
// NOTE this will still store shadowed names.
|
||||
// that's fine: the branch throws a runtime error anyway
|
||||
let ret = match store_pattern(env, &mono_pattern, cond_symbol, cond_layout, &mut stored) {
|
||||
Ok(_) => from_can(env, first.value.value, procs, None),
|
||||
Ok(_) => from_can(env, first.value.value, procs, layout_cache),
|
||||
Err(message) => Expr::RuntimeError(env.arena.alloc(message)),
|
||||
};
|
||||
|
||||
Expr::Store(stored.into_bump_slice(), arena.alloc(ret))
|
||||
} else {
|
||||
let cond_layout = Layout::from_var(env.arena, cond_var, env.subs, env.pointer_size)
|
||||
let cond_layout = layout_cache
|
||||
.from_var(env.arena, cond_var, env.subs, env.pointer_size)
|
||||
.unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err));
|
||||
|
||||
let cond = from_can(env, loc_cond.value, procs, None);
|
||||
let cond = from_can(env, loc_cond.value, procs, layout_cache);
|
||||
let cond_symbol = env.unique_symbol();
|
||||
|
||||
let mut loc_branches = std::vec::Vec::new();
|
||||
let mut opt_branches = std::vec::Vec::new();
|
||||
|
||||
for when_branch in branches {
|
||||
let mono_expr = from_can(env, when_branch.value.value, procs, None);
|
||||
let mono_expr = from_can(env, when_branch.value.value, procs, layout_cache);
|
||||
|
||||
let exhaustive_guard = if when_branch.guard.is_some() {
|
||||
Guard::HasGuard
|
||||
|
@ -1226,7 +1230,7 @@ fn from_can_when<'a>(
|
|||
//
|
||||
// otherwise, we modify the branch's expression to include the stores
|
||||
if let Some(loc_guard) = when_branch.guard.clone() {
|
||||
let expr = from_can(env, loc_guard.value, procs, None);
|
||||
let expr = from_can(env, loc_guard.value, procs, layout_cache);
|
||||
(
|
||||
crate::decision_tree::Guard::Guard {
|
||||
stores: stores.into_bump_slice(),
|
||||
|
@ -1306,7 +1310,8 @@ fn from_can_when<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
let ret_layout = Layout::from_var(env.arena, expr_var, env.subs, env.pointer_size)
|
||||
let ret_layout = layout_cache
|
||||
.from_var(env.arena, expr_var, env.subs, env.pointer_size)
|
||||
.unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err));
|
||||
|
||||
let branching = crate::decision_tree::optimize_when(
|
||||
|
@ -1330,6 +1335,7 @@ fn call_by_name<'a>(
|
|||
ret_var: Variable,
|
||||
proc_name: Symbol,
|
||||
loc_args: std::vec::Vec<(Variable, Located<roc_can::expr::Expr>)>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
) -> Expr<'a> {
|
||||
// create specialized procedure to call
|
||||
|
||||
|
@ -1339,79 +1345,96 @@ fn call_by_name<'a>(
|
|||
// get a borrow checker error about trying to borrow `procs` as mutable
|
||||
// while there is still an active immutable borrow.
|
||||
#[allow(clippy::type_complexity)]
|
||||
let opt_specialize_body: Option<(
|
||||
ContentHash,
|
||||
Variable,
|
||||
roc_can::expr::Expr,
|
||||
Vec<'a, Symbol>,
|
||||
)>;
|
||||
let opt_specialize_body: Option<(Variable, roc_can::expr::Expr, Vec<'a, Symbol>)>;
|
||||
|
||||
let specialized_proc_name = match procs.get_user_defined(proc_name) {
|
||||
Some(partial_proc) => {
|
||||
let content_hash = ContentHash::from_var(fn_var, env.subs);
|
||||
|
||||
match procs.specializations.get(&content_hash) {
|
||||
Some(specialization) => {
|
||||
opt_specialize_body = None;
|
||||
|
||||
// a specialization with this type hash already exists, so use its symbol
|
||||
specialization.0
|
||||
match layout_cache.from_var(env.arena, fn_var, env.subs, env.pointer_size) {
|
||||
Ok(layout) => {
|
||||
match procs.get_user_defined(proc_name) {
|
||||
Some(partial_proc) => {
|
||||
match procs
|
||||
.specializations
|
||||
.get(&proc_name)
|
||||
.and_then(|procs_by_layout| procs_by_layout.get(&layout))
|
||||
{
|
||||
Some(_) => {
|
||||
// a specialization with this layout already exists.
|
||||
opt_specialize_body = None;
|
||||
}
|
||||
None => {
|
||||
if procs.pending_specializations.get(&proc_name) == Some(&layout) {
|
||||
// If we're already in the process of specializing this, don't
|
||||
// try to specialize it further; otherwise, we'll loop forever.
|
||||
opt_specialize_body = None;
|
||||
} else {
|
||||
opt_specialize_body = Some((
|
||||
partial_proc.annotation,
|
||||
partial_proc.body.clone(),
|
||||
partial_proc.patterns.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
opt_specialize_body = Some((
|
||||
content_hash,
|
||||
partial_proc.annotation,
|
||||
partial_proc.body.clone(),
|
||||
partial_proc.patterns.clone(),
|
||||
));
|
||||
opt_specialize_body = None;
|
||||
|
||||
// generate a symbol for this specialization
|
||||
env.unique_symbol()
|
||||
// This happens for built-in symbols (they are never defined as a Closure)
|
||||
procs.insert_builtin(proc_name);
|
||||
}
|
||||
};
|
||||
|
||||
if let Some((annotation, body, loc_patterns)) = opt_specialize_body {
|
||||
// register proc, so specialization doesn't loop infinitely
|
||||
procs
|
||||
.pending_specializations
|
||||
.insert(proc_name, layout.clone());
|
||||
|
||||
let arg_vars = loc_args.iter().map(|v| v.0).collect::<std::vec::Vec<_>>();
|
||||
|
||||
match specialize_proc_body(
|
||||
env,
|
||||
procs,
|
||||
fn_var,
|
||||
ret_var,
|
||||
proc_name,
|
||||
&arg_vars,
|
||||
&loc_patterns,
|
||||
annotation,
|
||||
body,
|
||||
layout_cache,
|
||||
) {
|
||||
Ok(proc) => {
|
||||
procs.insert_specialization(proc_name, layout.clone(), proc);
|
||||
}
|
||||
Err(()) => {
|
||||
procs.runtime_errors.insert(proc_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generate actual call
|
||||
let mut args = Vec::with_capacity_in(loc_args.len(), env.arena);
|
||||
|
||||
for (var, loc_arg) in loc_args {
|
||||
let layout = layout_cache
|
||||
.from_var(&env.arena, var, &env.subs, env.pointer_size)
|
||||
.unwrap_or_else(|err| panic!("TODO gracefully handle bad layout: {:?}", err));
|
||||
|
||||
args.push((from_can(env, loc_arg.value, procs, layout_cache), layout));
|
||||
}
|
||||
|
||||
Expr::CallByName {
|
||||
name: proc_name,
|
||||
layout,
|
||||
args: args.into_bump_slice(),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
opt_specialize_body = None;
|
||||
|
||||
// This happens for built-in symbols (they are never defined as a Closure)
|
||||
procs.insert_builtin(proc_name);
|
||||
proc_name
|
||||
Err(()) => {
|
||||
// This function code gens to a runtime error,
|
||||
// so attempting to call it will immediately crash.
|
||||
Expr::RuntimeError("")
|
||||
}
|
||||
};
|
||||
|
||||
if let Some((content_hash, annotation, body, loc_patterns)) = opt_specialize_body {
|
||||
// register proc, so specialization doesn't loop infinitely
|
||||
procs.insert_specialization(content_hash, specialized_proc_name, None);
|
||||
|
||||
let arg_vars = loc_args.iter().map(|v| v.0).collect::<std::vec::Vec<_>>();
|
||||
|
||||
let proc = specialize_proc_body(
|
||||
env,
|
||||
procs,
|
||||
fn_var,
|
||||
ret_var,
|
||||
specialized_proc_name,
|
||||
&arg_vars,
|
||||
&loc_patterns,
|
||||
annotation,
|
||||
body,
|
||||
)
|
||||
.ok();
|
||||
|
||||
procs.insert_specialization(content_hash, specialized_proc_name, proc);
|
||||
}
|
||||
|
||||
// generate actual call
|
||||
let mut args = Vec::with_capacity_in(loc_args.len(), env.arena);
|
||||
|
||||
for (var, loc_arg) in loc_args {
|
||||
let layout = Layout::from_var(&env.arena, var, &env.subs, env.pointer_size)
|
||||
.unwrap_or_else(|err| panic!("TODO gracefully handle bad layout: {:?}", err));
|
||||
|
||||
args.push((from_can(env, loc_arg.value, procs, None), layout));
|
||||
}
|
||||
|
||||
Expr::CallByName(specialized_proc_name, args.into_bump_slice())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
@ -1425,23 +1448,26 @@ fn specialize_proc_body<'a>(
|
|||
pattern_symbols: &[Symbol],
|
||||
annotation: Variable,
|
||||
body: roc_can::expr::Expr,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
) -> Result<Proc<'a>, ()> {
|
||||
// unify the called function with the specialized signature, then specialize the function body
|
||||
let snapshot = env.subs.snapshot();
|
||||
let unified = roc_unify::unify::unify(env.subs, annotation, fn_var);
|
||||
debug_assert!(matches!(unified, roc_unify::unify::Unified::Success(_)));
|
||||
let specialized_body = from_can(env, body, procs, None);
|
||||
let specialized_body = from_can(env, body, procs, layout_cache);
|
||||
// reset subs, so we don't get type errors when specializing for a different signature
|
||||
env.subs.rollback_to(snapshot);
|
||||
|
||||
let mut proc_args = Vec::with_capacity_in(loc_args.len(), &env.arena);
|
||||
|
||||
for (arg_var, arg_name) in loc_args.iter().zip(pattern_symbols.iter()) {
|
||||
let layout = Layout::from_var(&env.arena, *arg_var, env.subs, env.pointer_size)?;
|
||||
let layout = layout_cache.from_var(&env.arena, *arg_var, env.subs, env.pointer_size)?;
|
||||
|
||||
proc_args.push((layout, *arg_name));
|
||||
}
|
||||
|
||||
let ret_layout = Layout::from_var(&env.arena, ret_var, env.subs, env.pointer_size)
|
||||
let ret_layout = layout_cache
|
||||
.from_var(&env.arena, ret_var, env.subs, env.pointer_size)
|
||||
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err));
|
||||
|
||||
let proc = Proc {
|
||||
|
@ -1752,7 +1778,7 @@ pub fn specialize_equality<'a>(
|
|||
rhs: Expr<'a>,
|
||||
layout: Layout<'a>,
|
||||
) -> Expr<'a> {
|
||||
let symbol = match &layout {
|
||||
let name = match &layout {
|
||||
Layout::Builtin(builtin) => match builtin {
|
||||
Builtin::Int64 => Symbol::INT_EQ_I64,
|
||||
Builtin::Float64 => Symbol::FLOAT_EQ,
|
||||
|
@ -1763,8 +1789,84 @@ pub fn specialize_equality<'a>(
|
|||
other => todo!("Cannot yet compare for equality {:?}", other),
|
||||
};
|
||||
|
||||
Expr::CallByName(
|
||||
symbol,
|
||||
arena.alloc([(lhs, layout.clone()), (rhs, layout.clone())]),
|
||||
)
|
||||
Expr::CallByName {
|
||||
name,
|
||||
layout: layout.clone(),
|
||||
args: arena.alloc([(lhs, layout.clone()), (rhs, layout)]),
|
||||
}
|
||||
}
|
||||
|
||||
fn specialize_builtin_functions<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
symbol: Symbol,
|
||||
loc_args: &[(Variable, Located<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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,21 +36,7 @@ pub enum Builtin<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Layout<'a> {
|
||||
/// Returns Err(()) if given an error, or Ok(Layout) if given a non-erroneous Structure.
|
||||
/// Panics if given a FlexVar or RigidVar, since those should have been
|
||||
/// monomorphized away already!
|
||||
pub fn from_var(
|
||||
arena: &'a Bump,
|
||||
var: Variable,
|
||||
subs: &Subs,
|
||||
pointer_size: u32,
|
||||
) -> Result<Self, ()> {
|
||||
let content = subs.get_without_compacting(var).content;
|
||||
|
||||
Self::from_content(arena, content, subs, pointer_size)
|
||||
}
|
||||
|
||||
pub fn from_content(
|
||||
pub fn new(
|
||||
arena: &'a Bump,
|
||||
content: Content,
|
||||
subs: &Subs,
|
||||
|
@ -61,7 +47,7 @@ impl<'a> Layout<'a> {
|
|||
match content {
|
||||
var @ FlexVar(_) | var @ RigidVar(_) => {
|
||||
panic!(
|
||||
"Layout::from_content encountered an unresolved {:?} - subs was {:?}",
|
||||
"Layout::new encountered an unresolved {:?} - subs was {:?}",
|
||||
var, subs
|
||||
);
|
||||
}
|
||||
|
@ -75,7 +61,7 @@ impl<'a> Layout<'a> {
|
|||
debug_assert!(args.is_empty());
|
||||
Ok(Layout::Builtin(Builtin::Float64))
|
||||
}
|
||||
Alias(_, _, var) => Self::from_content(
|
||||
Alias(_, _, var) => Self::new(
|
||||
arena,
|
||||
subs.get_without_compacting(var).content,
|
||||
subs,
|
||||
|
@ -85,6 +71,20 @@ impl<'a> Layout<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns Err(()) if given an error, or Ok(Layout) if given a non-erroneous Structure.
|
||||
/// Panics if given a FlexVar or RigidVar, since those should have been
|
||||
/// monomorphized away already!
|
||||
fn from_var(
|
||||
arena: &'a Bump,
|
||||
var: Variable,
|
||||
subs: &Subs,
|
||||
pointer_size: u32,
|
||||
) -> Result<Self, ()> {
|
||||
let content = subs.get_without_compacting(var).content;
|
||||
|
||||
Self::new(arena, content, subs, pointer_size)
|
||||
}
|
||||
|
||||
pub fn safe_to_memcpy(&self) -> bool {
|
||||
use Layout::*;
|
||||
|
||||
|
@ -137,6 +137,37 @@ impl<'a> Layout<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Avoid recomputing Layout from Variable multiple times.
|
||||
#[derive(Default)]
|
||||
pub struct LayoutCache<'a> {
|
||||
layouts: MutMap<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> {
|
||||
const I64_SIZE: u32 = std::mem::size_of::<i64>() 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 {
|
||||
FlexVar(_) | RigidVar(_) => Ok(Layout::Builtin(Builtin::EmptyList)),
|
||||
content => {
|
||||
let elem_layout =
|
||||
Layout::from_content(arena, content, subs, pointer_size)?;
|
||||
let elem_layout = Layout::new(arena, content, subs, pointer_size)?;
|
||||
|
||||
Ok(Layout::Builtin(Builtin::List(arena.alloc(elem_layout))))
|
||||
}
|
||||
|
@ -246,16 +276,11 @@ fn layout_from_flat_type<'a>(
|
|||
for arg_var in args {
|
||||
let arg_content = subs.get_without_compacting(arg_var).content;
|
||||
|
||||
fn_args.push(Layout::from_content(
|
||||
arena,
|
||||
arg_content,
|
||||
subs,
|
||||
pointer_size,
|
||||
)?);
|
||||
fn_args.push(Layout::new(arena, arg_content, subs, pointer_size)?);
|
||||
}
|
||||
|
||||
let ret_content = subs.get_without_compacting(ret_var).content;
|
||||
let ret = Layout::from_content(arena, ret_content, subs, pointer_size)?;
|
||||
let ret = Layout::new(arena, ret_content, subs, pointer_size)?;
|
||||
|
||||
Ok(Layout::FunctionPointer(
|
||||
fn_args.into_bump_slice(),
|
||||
|
@ -273,14 +298,13 @@ fn layout_from_flat_type<'a>(
|
|||
|
||||
for (_, field_var) in btree {
|
||||
let field_content = subs.get_without_compacting(field_var).content;
|
||||
let field_layout =
|
||||
match Layout::from_content(arena, field_content, subs, pointer_size) {
|
||||
Ok(layout) => layout,
|
||||
Err(()) => {
|
||||
// Invalid field!
|
||||
panic!("TODO gracefully handle record with invalid field.var");
|
||||
}
|
||||
};
|
||||
let field_layout = match Layout::new(arena, field_content, subs, pointer_size) {
|
||||
Ok(layout) => layout,
|
||||
Err(()) => {
|
||||
// Invalid field!
|
||||
panic!("TODO gracefully handle record with invalid field.var");
|
||||
}
|
||||
};
|
||||
|
||||
layouts.push(field_layout);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
// and encouraging shortcuts here creates bad incentives. I would rather temporarily
|
||||
// re-enable this when working on performance optimizations than have it block PRs.
|
||||
#![allow(clippy::large_enum_variant)]
|
||||
|
||||
pub mod expr;
|
||||
pub mod layout;
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ mod test_mono {
|
|||
// Put this module's ident_ids back in the interns
|
||||
interns.all_ident_ids.insert(home, ident_ids);
|
||||
|
||||
assert_eq!(mono_expr, get_expected(interns));
|
||||
assert_eq!(get_expected(interns), mono_expr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -84,13 +84,20 @@ mod test_mono {
|
|||
fn float_addition() {
|
||||
compiles_to(
|
||||
"3.0 + 4",
|
||||
CallByName(
|
||||
Symbol::FLOAT_ADD,
|
||||
&[
|
||||
CallByName {
|
||||
name: Symbol::FLOAT_ADD,
|
||||
layout: Layout::FunctionPointer(
|
||||
&[
|
||||
Layout::Builtin(Builtin::Float64),
|
||||
Layout::Builtin(Builtin::Float64),
|
||||
],
|
||||
&Layout::Builtin(Builtin::Float64),
|
||||
),
|
||||
args: &[
|
||||
(Float(3.0), Layout::Builtin(Builtin::Float64)),
|
||||
(Float(4.0), Layout::Builtin(Builtin::Float64)),
|
||||
],
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -98,13 +105,20 @@ mod test_mono {
|
|||
fn int_addition() {
|
||||
compiles_to(
|
||||
"0xDEADBEEF + 4",
|
||||
CallByName(
|
||||
Symbol::INT_ADD,
|
||||
&[
|
||||
CallByName {
|
||||
name: Symbol::INT_ADD,
|
||||
layout: Layout::FunctionPointer(
|
||||
&[
|
||||
Layout::Builtin(Builtin::Int64),
|
||||
Layout::Builtin(Builtin::Int64),
|
||||
],
|
||||
&Layout::Builtin(Builtin::Int64),
|
||||
),
|
||||
args: &[
|
||||
(Int(3735928559), Layout::Builtin(Builtin::Int64)),
|
||||
(Int(4), Layout::Builtin(Builtin::Int64)),
|
||||
],
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -113,13 +127,20 @@ mod test_mono {
|
|||
// Default to Int for `Num *`
|
||||
compiles_to(
|
||||
"3 + 5",
|
||||
CallByName(
|
||||
Symbol::INT_ADD,
|
||||
&[
|
||||
CallByName {
|
||||
name: Symbol::INT_ADD,
|
||||
layout: Layout::FunctionPointer(
|
||||
&[
|
||||
Layout::Builtin(Builtin::Int64),
|
||||
Layout::Builtin(Builtin::Int64),
|
||||
],
|
||||
&Layout::Builtin(Builtin::Int64),
|
||||
),
|
||||
args: &[
|
||||
(Int(3), Layout::Builtin(Builtin::Int64)),
|
||||
(Int(5), Layout::Builtin(Builtin::Int64)),
|
||||
],
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -133,20 +154,31 @@ mod test_mono {
|
|||
"#,
|
||||
{
|
||||
use self::Builtin::*;
|
||||
use Layout::Builtin;
|
||||
let home = test_home();
|
||||
|
||||
let gen_symbol_3 = Interns::from_index(home, 3);
|
||||
let gen_symbol_4 = Interns::from_index(home, 4);
|
||||
let gen_symbol_0 = Interns::from_index(home, 0);
|
||||
|
||||
Struct(&[
|
||||
(
|
||||
CallByName(gen_symbol_3, &[(Int(4), Builtin(Int64))]),
|
||||
Builtin(Int64),
|
||||
CallByName {
|
||||
name: gen_symbol_0,
|
||||
layout: Layout::FunctionPointer(
|
||||
&[Layout::Builtin(Builtin::Int64)],
|
||||
&Layout::Builtin(Builtin::Int64),
|
||||
),
|
||||
args: &[(Int(4), Layout::Builtin(Int64))],
|
||||
},
|
||||
Layout::Builtin(Int64),
|
||||
),
|
||||
(
|
||||
CallByName(gen_symbol_4, &[(Float(3.14), Builtin(Float64))]),
|
||||
Builtin(Float64),
|
||||
CallByName {
|
||||
name: gen_symbol_0,
|
||||
layout: Layout::FunctionPointer(
|
||||
&[Layout::Builtin(Builtin::Float64)],
|
||||
&Layout::Builtin(Builtin::Float64),
|
||||
),
|
||||
args: &[(Float(3.14), Layout::Builtin(Float64))],
|
||||
},
|
||||
Layout::Builtin(Float64),
|
||||
),
|
||||
])
|
||||
},
|
||||
|
@ -314,22 +346,31 @@ mod test_mono {
|
|||
"#,
|
||||
{
|
||||
use self::Builtin::*;
|
||||
use Layout::Builtin;
|
||||
let home = test_home();
|
||||
|
||||
let gen_symbol_3 = Interns::from_index(home, 3);
|
||||
let gen_symbol_4 = Interns::from_index(home, 4);
|
||||
let gen_symbol_0 = Interns::from_index(home, 0);
|
||||
|
||||
CallByName(
|
||||
gen_symbol_3,
|
||||
&[(
|
||||
CallByName {
|
||||
name: gen_symbol_0,
|
||||
layout: Layout::FunctionPointer(
|
||||
&[Layout::Struct(&[Layout::Builtin(Builtin::Int64)])],
|
||||
&Layout::Struct(&[Layout::Builtin(Builtin::Int64)]),
|
||||
),
|
||||
args: &[(
|
||||
Struct(&[(
|
||||
CallByName(gen_symbol_4, &[(Int(4), Builtin(Int64))]),
|
||||
Builtin(Int64),
|
||||
CallByName {
|
||||
name: gen_symbol_0,
|
||||
layout: Layout::FunctionPointer(
|
||||
&[Layout::Builtin(Builtin::Int64)],
|
||||
&Layout::Builtin(Builtin::Int64),
|
||||
),
|
||||
args: &[(Int(4), Layout::Builtin(Int64))],
|
||||
},
|
||||
Layout::Builtin(Int64),
|
||||
)]),
|
||||
Layout::Struct(&[Builtin(Int64)]),
|
||||
Layout::Struct(&[Layout::Builtin(Int64)]),
|
||||
)],
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -464,13 +505,30 @@ mod test_mono {
|
|||
#[test]
|
||||
fn set_unique_int_list() {
|
||||
compiles_to("List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1", {
|
||||
CallByName(
|
||||
Symbol::LIST_GET_UNSAFE,
|
||||
&vec![
|
||||
CallByName {
|
||||
name: Symbol::LIST_GET_UNSAFE,
|
||||
layout: Layout::FunctionPointer(
|
||||
&[
|
||||
Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
|
||||
Layout::Builtin(Builtin::Int64),
|
||||
],
|
||||
&Layout::Builtin(Builtin::Int64),
|
||||
),
|
||||
args: &vec![
|
||||
(
|
||||
CallByName(
|
||||
Symbol::LIST_SET,
|
||||
&vec![
|
||||
CallByName {
|
||||
name: Symbol::LIST_SET,
|
||||
layout: Layout::FunctionPointer(
|
||||
&[
|
||||
Layout::Builtin(Builtin::List(&Layout::Builtin(
|
||||
Builtin::Int64,
|
||||
))),
|
||||
Layout::Builtin(Builtin::Int64),
|
||||
Layout::Builtin(Builtin::Int64),
|
||||
],
|
||||
&Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
|
||||
),
|
||||
args: &vec![
|
||||
(
|
||||
Array {
|
||||
elem_layout: Layout::Builtin(Builtin::Int64),
|
||||
|
@ -483,12 +541,12 @@ mod test_mono {
|
|||
(Int(1), Layout::Builtin(Builtin::Int64)),
|
||||
(Int(42), Layout::Builtin(Builtin::Int64)),
|
||||
],
|
||||
),
|
||||
},
|
||||
Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
|
||||
),
|
||||
(Int(1), Layout::Builtin(Builtin::Int64)),
|
||||
],
|
||||
)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -79,8 +79,15 @@ mod test_opt {
|
|||
unexpected_calls: &mut Vec<Symbol>,
|
||||
) {
|
||||
match expr {
|
||||
Int(_) | Float(_) | Str(_) | Bool(_) | Byte(_) | Load(_) | FunctionPointer(_)
|
||||
| RuntimeError(_) => (),
|
||||
Int(_)
|
||||
| Float(_)
|
||||
| Str(_)
|
||||
| Bool(_)
|
||||
| Byte(_)
|
||||
| Load(_)
|
||||
| FunctionPointer(_, _)
|
||||
| RuntimeError(_)
|
||||
| RuntimeErrorFunction(_) => (),
|
||||
|
||||
Store(paths, sub_expr) => {
|
||||
for (_, _, path_expr) in paths.iter() {
|
||||
|
@ -98,15 +105,19 @@ mod test_opt {
|
|||
}
|
||||
}
|
||||
|
||||
CallByName(symbol, args) => {
|
||||
CallByName {
|
||||
name,
|
||||
layout: _,
|
||||
args,
|
||||
} => {
|
||||
// Search for the symbol. If we found it, check it off the list.
|
||||
// If we didn't find it, add it to the list of unexpected calls.
|
||||
match calls.binary_search(symbol) {
|
||||
match calls.binary_search(name) {
|
||||
Ok(index) => {
|
||||
calls.remove(index);
|
||||
}
|
||||
Err(_) => {
|
||||
unexpected_calls.push(*symbol);
|
||||
unexpected_calls.push(*name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -222,13 +233,30 @@ mod test_opt {
|
|||
// This should optimize List.set to List.set_in_place
|
||||
compiles_to(
|
||||
"List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1",
|
||||
CallByName(
|
||||
Symbol::LIST_GET_UNSAFE,
|
||||
&vec![
|
||||
CallByName {
|
||||
name: Symbol::LIST_GET_UNSAFE,
|
||||
layout: Layout::FunctionPointer(
|
||||
&[
|
||||
Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
|
||||
Layout::Builtin(Builtin::Int64),
|
||||
],
|
||||
&Layout::Builtin(Builtin::Int64),
|
||||
),
|
||||
args: &vec![
|
||||
(
|
||||
CallByName(
|
||||
Symbol::LIST_SET_IN_PLACE,
|
||||
&vec![
|
||||
CallByName {
|
||||
name: Symbol::LIST_SET_IN_PLACE,
|
||||
layout: Layout::FunctionPointer(
|
||||
&[
|
||||
Layout::Builtin(Builtin::List(&Layout::Builtin(
|
||||
Builtin::Int64,
|
||||
))),
|
||||
Layout::Builtin(Builtin::Int64),
|
||||
Layout::Builtin(Builtin::Int64),
|
||||
],
|
||||
&Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
|
||||
),
|
||||
args: &vec![
|
||||
(
|
||||
Array {
|
||||
elem_layout: Layout::Builtin(Builtin::Int64),
|
||||
|
@ -241,12 +269,12 @@ mod test_opt {
|
|||
(Int(1), Layout::Builtin(Builtin::Int64)),
|
||||
(Int(42), Layout::Builtin(Builtin::Int64)),
|
||||
],
|
||||
),
|
||||
},
|
||||
Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
|
||||
),
|
||||
(Int(1), Layout::Builtin(Builtin::Int64)),
|
||||
],
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -562,250 +562,6 @@ pub enum FlatType {
|
|||
Boolean(boolean_algebra::Bool),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq, Copy)]
|
||||
pub struct ContentHash(u64);
|
||||
|
||||
impl ContentHash {
|
||||
pub fn from_var(var: Variable, subs: &mut Subs) -> Self {
|
||||
use std::hash::Hasher;
|
||||
|
||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||
Self::from_var_help(var, subs, &mut hasher);
|
||||
|
||||
ContentHash(hasher.finish())
|
||||
}
|
||||
|
||||
pub fn from_var_help<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)]
|
||||
pub enum Builtin {
|
||||
Str,
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
#version 450
|
||||
#extension GL_ARB_separate_shader_objects : enable
|
||||
|
||||
layout(location = 0) in vec4 vertex_color;
|
||||
|
||||
layout(location = 0) out vec4 fragment_color;
|
||||
|
||||
void main() {
|
||||
fragment_color = vec4(0.5, 0.5, 1.0, 1.0);
|
||||
fragment_color = vertex_color;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
#version 450
|
||||
#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() {
|
||||
vec2 position;
|
||||
if (gl_VertexIndex == 0) {
|
||||
|
@ -11,5 +19,7 @@ void main() {
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use glsl_to_spirv::ShaderType;
|
|||
use std::io;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::path::Path;
|
||||
use winit::event::{ElementState, ModifiersState, VirtualKeyCode};
|
||||
|
||||
/// 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.
|
||||
|
@ -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() {
|
||||
// TODO do a better window size
|
||||
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 (logical_window_size, physical_window_size) = {
|
||||
|
@ -191,8 +205,12 @@ fn run_event_loop() {
|
|||
};
|
||||
|
||||
let pipeline_layout = unsafe {
|
||||
use gfx_hal::pso::ShaderStageFlags;
|
||||
|
||||
let push_constant_bytes = std::mem::size_of::<PushConstants>() as u32;
|
||||
|
||||
device
|
||||
.create_pipeline_layout(&[], &[])
|
||||
.create_pipeline_layout(&[], &[(ShaderStageFlags::VERTEX, 0..push_constant_bytes)])
|
||||
.expect("Out of memory")
|
||||
};
|
||||
|
||||
|
@ -295,16 +313,21 @@ fn run_event_loop() {
|
|||
submission_complete_fence,
|
||||
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| {
|
||||
use winit::event::{Event, WindowEvent};
|
||||
use winit::event_loop::ControlFlow;
|
||||
|
||||
// TODO try ControlFlow::Poll and see if it affects input latency.
|
||||
// Otherwise, this seems like a better default for minimizing idle
|
||||
// CPU usage and battry drain. (Might want to switch to Poll whenever
|
||||
// there are animations in progress though.)
|
||||
*control_flow = ControlFlow::Wait;
|
||||
// TODO dynamically switch this on/off depending on whether any
|
||||
// animations are running. Should conserve CPU usage and battery life!
|
||||
if is_animating {
|
||||
*control_flow = ControlFlow::Poll;
|
||||
} else {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
|
@ -322,6 +345,16 @@ fn run_event_loop() {
|
|||
};
|
||||
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, .. } => {
|
||||
surface_extent = Extent2D {
|
||||
width: new_inner_size.width,
|
||||
|
@ -329,14 +362,34 @@ fn run_event_loop() {
|
|||
};
|
||||
should_configure_swapchain = true;
|
||||
}
|
||||
WindowEvent::ModifiersChanged(modifiers) => {
|
||||
keyboard_modifiers = modifiers;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::MainEventsCleared => window.request_redraw(),
|
||||
Event::RedrawRequested(_) => {
|
||||
let res: &mut Resources<_> = &mut resource_holder.0;
|
||||
let render_pass = &res.render_passes[0];
|
||||
let pipeline_layout = &res.pipeline_layouts[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 {
|
||||
use gfx_hal::pool::CommandPool;
|
||||
|
||||
|
@ -444,7 +497,20 @@ fn run_event_loop() {
|
|||
SubpassContents::Inline,
|
||||
);
|
||||
command_buffer.bind_graphics_pipeline(pipeline);
|
||||
command_buffer.draw(0..3, 0..1);
|
||||
|
||||
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.end_render_pass();
|
||||
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")
|
||||
}
|
||||
|
||||
/// 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ pkgs }: [
|
||||
pkgs.rustup
|
||||
pkgs.cargo
|
||||
pkgs.llvm_8
|
||||
pkgs.llvm_10
|
||||
# libraries for llvm
|
||||
pkgs.libffi
|
||||
pkgs.libxml2
|
||||
|
|
|
@ -225,7 +225,95 @@ when color is
|
|||
|
||||
## 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
|
||||
> (e.g. multiple consecutive `Task.andThen`s between tasks with incompatible error types),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue