Merge branch 'trunk' of github.com:rtfeldman/roc into dict

This commit is contained in:
Chadtech 2021-01-16 14:55:09 -05:00
commit c7f6de2afe
93 changed files with 6706 additions and 5317 deletions

View file

@ -8,7 +8,7 @@ To build the compiler, you need these installed:
* `libunwind` (macOS should already have this one installed) * `libunwind` (macOS should already have this one installed)
* `libc++-dev` * `libc++-dev`
* Python 2.7 (Windows only), `python-is-python3` (Ubuntu) * Python 2.7 (Windows only), `python-is-python3` (Ubuntu)
* a particular version of Zig (see below) * [Zig](https://ziglang.org/) 0.7.1 or greater
* a particular version of LLVM (see below) * a particular version of LLVM (see below)
To run the test suite (via `cargo test`), you additionally need to install: To run the test suite (via `cargo test`), you additionally need to install:
@ -24,32 +24,10 @@ MacOS systems should already have `libunwind`, but other systems will need to in
Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev`.) Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev`.)
### Zig ### Zig
We use a specific version of Zig, a build off the the commit `0088efc4b`. The latest tagged version of Zig, 0.6.0, doesn't include the feature to emit LLVM ir, which is a core feature of how we use Zig. To download this specific version, you can:
* use the following commands on Debian/Ubuntu (on other distros, steps should be essentially the same):
```
cd /tmp
# download the files
wget https://ziglang.org/builds/zig-linux-x86_64-0.6.0+0088efc4b.tar.xz
# uncompress:
xz -d zig-linux-x86_64-0.6.0+0088efc4b.tar.xz
# untar:
tar xvf zig-linux-x86_64-0.6.0+0088efc4b.tar
# move the files into /opt:
sudo mkdir -p /opt/zig
sudo mv zig-linux-x86_64-0.6.0+0088efc4b/* /opt/zig/
```
Then add `/opt/zig/` to your `PATH` (e.g. in `~/.bashrc`).
Reload your `.bashrc` file: `source ~/.bashrc` and test that `zig` is If you're on MacOS, you can install with `brew install zig`
an available command. If you're on Ubuntu and use Snap, you can install with `snap install zig --classic --beta`
For any other OS, checkout the [Zig installation page](https://github.com/ziglang/zig/wiki/Install-Zig-from-a-Package-Manager)
* [macOS](https://ziglang.org/builds/zig-macos-x86_64-0.6.0+0088efc4b.tar.xz)
Alternatively, any recent master branch build should work. To install the latest master branch build you can use:
* `brew install zig --HEAD` (on macos)
* `snap install zig --classic --edge` (on ubunutu)
Once 0.7.0 is released, we'll switch back to installing the tagged releases and this process will get easier.
### LLVM ### LLVM

2
Cargo.lock generated
View file

@ -2672,6 +2672,8 @@ dependencies = [
"log", "log",
"maplit", "maplit",
"page_size", "page_size",
"pest",
"pest_derive",
"pretty_assertions", "pretty_assertions",
"quickcheck", "quickcheck",
"quickcheck_macros", "quickcheck_macros",

View file

@ -29,6 +29,7 @@ pub fn build_file(
) -> Result<PathBuf, LoadingProblem> { ) -> Result<PathBuf, LoadingProblem> {
let compilation_start = SystemTime::now(); let compilation_start = SystemTime::now();
let arena = Bump::new(); let arena = Bump::new();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
// Step 1: compile the app and generate the .o file // Step 1: compile the app and generate the .o file
let subs_by_module = MutMap::default(); let subs_by_module = MutMap::default();
@ -36,14 +37,15 @@ pub fn build_file(
// Release builds use uniqueness optimizations // Release builds use uniqueness optimizations
let stdlib = match opt_level { let stdlib = match opt_level {
OptLevel::Normal => roc_builtins::std::standard_stdlib(), OptLevel::Normal => roc_builtins::std::standard_stdlib(),
OptLevel::Optimize => roc_builtins::unique::uniq_stdlib(), OptLevel::Optimize => roc_builtins::std::standard_stdlib(),
}; };
let loaded = roc_load::file::load_and_monomorphize( let loaded = roc_load::file::load_and_monomorphize(
&arena, &arena,
roc_file_path.clone(), roc_file_path.clone(),
stdlib, &stdlib,
src_dir.as_path(), src_dir.as_path(),
subs_by_module, subs_by_module,
ptr_bytes,
)?; )?;
let path_to_platform = loaded.platform_path.clone(); let path_to_platform = loaded.platform_path.clone();

View file

@ -72,6 +72,9 @@ fn jit_to_ast_help<'a>(
run_jit_function!(lib, main_fn_name, u8, |num| byte_to_ast(env, num, content)), run_jit_function!(lib, main_fn_name, u8, |num| byte_to_ast(env, num, content)),
) )
} }
Layout::Builtin(Builtin::Usize) => Ok(run_jit_function!(lib, main_fn_name, usize, |num| {
num_to_ast(env, nat_to_ast(env.arena, num), content)
})),
Layout::Builtin(Builtin::Int64) => { Layout::Builtin(Builtin::Int64) => {
Ok(run_jit_function!(lib, main_fn_name, i64, |num| num_to_ast( Ok(run_jit_function!(lib, main_fn_name, i64, |num| num_to_ast(
env, env,
@ -161,7 +164,10 @@ fn jit_to_ast_help<'a>(
let size = layout.stack_size(env.ptr_bytes); let size = layout.stack_size(env.ptr_bytes);
match union_variant { match union_variant {
UnionVariant::Wrapped(tags_and_layouts) => { UnionVariant::Wrapped {
sorted_tag_layouts: tags_and_layouts,
..
} => {
Ok(run_jit_function_dynamic_type!( Ok(run_jit_function_dynamic_type!(
lib, lib,
main_fn_name, main_fn_name,
@ -242,6 +248,11 @@ fn ptr_to_ast<'a>(
num_to_ast(env, i64_to_ast(env.arena, num), content) num_to_ast(env, i64_to_ast(env.arena, num), content)
} }
Layout::Builtin(Builtin::Usize) => {
let num = unsafe { *(ptr as *const usize) };
num_to_ast(env, nat_to_ast(env.arena, num), content)
}
Layout::Builtin(Builtin::Int1) => { Layout::Builtin(Builtin::Int1) => {
// TODO: bits are not as expected here. // TODO: bits are not as expected here.
// num is always false at the moment. // num is always false at the moment.
@ -812,6 +823,12 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E
} }
} }
/// This is centralized in case we want to format it differently later,
/// e.g. adding underscores for large numbers
fn nat_to_ast(arena: &Bump, num: usize) -> Expr<'_> {
Expr::Num(arena.alloc(format!("{}", num)))
}
/// This is centralized in case we want to format it differently later, /// This is centralized in case we want to format it differently later,
/// e.g. adding underscores for large numbers /// e.g. adding underscores for large numbers
fn i64_to_ast(arena: &Bump, num: i64) -> Expr<'_> { fn i64_to_ast(arena: &Bump, num: i64) -> Expr<'_> {

View file

@ -35,14 +35,17 @@ pub fn gen_and_eval(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<R
let module_src = promote_expr_to_module(src_str); let module_src = promote_expr_to_module(src_str);
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let exposed_types = MutMap::default(); let exposed_types = MutMap::default();
let loaded = roc_load::file::load_and_monomorphize_from_str( let loaded = roc_load::file::load_and_monomorphize_from_str(
&arena, &arena,
filename, filename,
&module_src, &module_src,
stdlib, &stdlib,
src_dir, src_dir,
exposed_types, exposed_types,
ptr_bytes,
); );
let mut loaded = loaded.expect("failed to load module"); let mut loaded = loaded.expect("failed to load module");

View file

@ -54,22 +54,22 @@ mod repl_eval {
#[test] #[test]
fn literal_0x0() { fn literal_0x0() {
expect_success("0x0", "0 : I64"); expect_success("0x0", "0 : Int *");
} }
#[test] #[test]
fn literal_0x42() { fn literal_0x42() {
expect_success("0x42", "66 : I64"); expect_success("0x42", "66 : Int *");
} }
#[test] #[test]
fn literal_0point0() { fn literal_0point0() {
expect_success("0.0", "0 : F64"); expect_success("0.0", "0 : Float *");
} }
#[test] #[test]
fn literal_4point2() { fn literal_4point2() {
expect_success("4.2", "4.2 : F64"); expect_success("4.2", "4.2 : Float *");
} }
#[test] #[test]
@ -89,7 +89,7 @@ mod repl_eval {
#[test] #[test]
fn num_rem() { fn num_rem() {
expect_success("299 % 10", "Ok 9 : Result I64 [ DivByZero ]*"); expect_success("299 % 10", "Ok 9 : Result (Int *) [ DivByZero ]*");
} }
#[test] #[test]
@ -153,7 +153,7 @@ mod repl_eval {
#[test] #[test]
fn single_element_tag_union() { fn single_element_tag_union() {
expect_success("True 1", "True 1 : [ True (Num *) ]*"); expect_success("True 1", "True 1 : [ True (Num *) ]*");
expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) F64 ]*"); expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) (Float *) ]*");
} }
#[test] #[test]
@ -162,7 +162,7 @@ mod repl_eval {
expect_success( expect_success(
"if 1 == 1 then True 3 else False 3.14", "if 1 == 1 then True 3 else False 3.14",
"True 3 : [ False F64, True (Num *) ]*", "True 3 : [ False (Float *), True (Num *) ]*",
) )
} }
@ -191,7 +191,7 @@ mod repl_eval {
#[test] #[test]
fn str_count_graphemes() { fn str_count_graphemes() {
expect_success("Str.countGraphemes \"å🤔\"", "2 : I64"); expect_success("Str.countGraphemes \"å🤔\"", "2 : Nat");
} }
#[test] #[test]
@ -206,12 +206,12 @@ mod repl_eval {
#[test] #[test]
fn literal_int_list() { fn literal_int_list() {
expect_success("[ 0x1, 0x2, 0x3 ]", "[ 1, 2, 3 ] : List I64"); expect_success("[ 0x1, 0x2, 0x3 ]", "[ 1, 2, 3 ] : List (Int *)");
} }
#[test] #[test]
fn literal_float_list() { fn literal_float_list() {
expect_success("[ 1.1, 2.2, 3.3 ]", "[ 1.1, 2.2, 3.3 ] : List F64"); expect_success("[ 1.1, 2.2, 3.3 ]", "[ 1.1, 2.2, 3.3 ] : List (Float *)");
} }
#[test] #[test]
@ -253,26 +253,37 @@ mod repl_eval {
#[test] #[test]
fn num_bitwise_and() { fn num_bitwise_and() {
expect_success("Num.bitwiseAnd 20 20", "20 : I64"); expect_success("Num.bitwiseAnd 20 20", "20 : Int *");
expect_success("Num.bitwiseAnd 25 10", "8 : I64"); expect_success("Num.bitwiseAnd 25 10", "8 : Int *");
expect_success("Num.bitwiseAnd 200 0", "0 : I64") expect_success("Num.bitwiseAnd 200 0", "0 : Int *")
}
#[test]
fn num_bitwise_xor() {
expect_success("Num.bitwiseXor 20 20", "0 : Int *");
expect_success("Num.bitwiseXor 15 14", "1 : Int *");
expect_success("Num.bitwiseXor 7 15", "8 : Int *");
expect_success("Num.bitwiseXor 200 0", "200 : Int *")
} }
#[test] #[test]
fn num_add_wrap() { fn num_add_wrap() {
expect_success("Num.addWrap Num.maxInt 1", "-9223372036854775808 : I64"); expect_success("Num.addWrap Num.maxInt 1", "-9223372036854775808 : Int *");
} }
#[test] #[test]
fn num_sub_wrap() { fn num_sub_wrap() {
expect_success("Num.subWrap Num.minInt 1", "9223372036854775807 : I64"); expect_success("Num.subWrap Num.minInt 1", "9223372036854775807 : Int *");
} }
#[test] #[test]
fn num_mul_wrap() { fn num_mul_wrap() {
expect_success("Num.mulWrap Num.maxInt 2", "-2 : I64"); expect_success("Num.mulWrap Num.maxInt 2", "-2 : Int *");
} }
#[test] #[test]
@ -309,7 +320,7 @@ mod repl_eval {
fn list_concat() { fn list_concat() {
expect_success( expect_success(
"List.concat [ 1.1, 2.2 ] [ 3.3, 4.4, 5.5 ]", "List.concat [ 1.1, 2.2 ] [ 3.3, 4.4, 5.5 ]",
"[ 1.1, 2.2, 3.3, 4.4, 5.5 ] : List F64", "[ 1.1, 2.2, 3.3, 4.4, 5.5 ] : List (Float *)",
); );
} }
@ -368,7 +379,7 @@ mod repl_eval {
fn basic_1_field_f64_record() { fn basic_1_field_f64_record() {
// Even though this gets unwrapped at runtime, the repl should still // Even though this gets unwrapped at runtime, the repl should still
// report it as a record // report it as a record
expect_success("{ foo: 4.2 }", "{ foo: 4.2 } : { foo : F64 }"); expect_success("{ foo: 4.2 }", "{ foo: 4.2 } : { foo : Float * }");
} }
#[test] #[test]
@ -387,7 +398,7 @@ mod repl_eval {
// report it as a record // report it as a record
expect_success( expect_success(
"{ foo: { bar: { baz: 4.2 } } }", "{ foo: { bar: { baz: 4.2 } } }",
"{ foo: { bar: { baz: 4.2 } } } : { foo : { bar : { baz : F64 } } }", "{ foo: { bar: { baz: 4.2 } } } : { foo : { bar : { baz : Float * } } }",
); );
} }
@ -395,7 +406,7 @@ mod repl_eval {
fn basic_2_field_i64_record() { fn basic_2_field_i64_record() {
expect_success( expect_success(
"{ foo: 0x4, bar: 0x2 }", "{ foo: 0x4, bar: 0x2 }",
"{ bar: 2, foo: 4 } : { bar : I64, foo : I64 }", "{ bar: 2, foo: 4 } : { bar : Int *, foo : Int * }",
); );
} }
@ -403,7 +414,7 @@ mod repl_eval {
fn basic_2_field_f64_record() { fn basic_2_field_f64_record() {
expect_success( expect_success(
"{ foo: 4.1, bar: 2.3 }", "{ foo: 4.1, bar: 2.3 }",
"{ bar: 2.3, foo: 4.1 } : { bar : F64, foo : F64 }", "{ bar: 2.3, foo: 4.1 } : { bar : Float *, foo : Float * }",
); );
} }
@ -411,7 +422,7 @@ mod repl_eval {
fn basic_2_field_mixed_record() { fn basic_2_field_mixed_record() {
expect_success( expect_success(
"{ foo: 4.1, bar: 2 }", "{ foo: 4.1, bar: 2 }",
"{ bar: 2, foo: 4.1 } : { bar : Num *, foo : F64 }", "{ bar: 2, foo: 4.1 } : { bar : Num *, foo : Float * }",
); );
} }
@ -419,7 +430,7 @@ mod repl_eval {
fn basic_3_field_record() { fn basic_3_field_record() {
expect_success( expect_success(
"{ foo: 4.1, bar: 2, baz: 0x5 }", "{ foo: 4.1, bar: 2, baz: 0x5 }",
"{ bar: 2, baz: 5, foo: 4.1 } : { bar : Num *, baz : I64, foo : F64 }", "{ bar: 2, baz: 5, foo: 4.1 } : { bar : Num *, baz : Int *, foo : Float * }",
); );
} }
@ -434,7 +445,7 @@ mod repl_eval {
fn list_of_2_field_records() { fn list_of_2_field_records() {
expect_success( expect_success(
"[ { foo: 4.1, bar: 2 } ]", "[ { foo: 4.1, bar: 2 } ]",
"[ { bar: 2, foo: 4.1 } ] : List { bar : Num *, foo : F64 }", "[ { bar: 2, foo: 4.1 } ] : List { bar : Num *, foo : Float * }",
); );
} }
@ -466,7 +477,7 @@ mod repl_eval {
fn list_of_3_field_records() { fn list_of_3_field_records() {
expect_success( expect_success(
"[ { foo: 4.1, bar: 2, baz: 0x3 } ]", "[ { foo: 4.1, bar: 2, baz: 0x3 } ]",
"[ { bar: 2, baz: 3, foo: 4.1 } ] : List { bar : Num *, baz : I64, foo : F64 }", "[ { bar: 2, baz: 3, foo: 4.1 } ] : List { bar : Num *, baz : Int *, foo : Float * }",
); );
} }

View file

@ -29,6 +29,7 @@ comptime {
exportStrFn(str.strConcatC, "concat"); exportStrFn(str.strConcatC, "concat");
exportStrFn(str.strNumberOfBytes, "number_of_bytes"); exportStrFn(str.strNumberOfBytes, "number_of_bytes");
exportStrFn(str.strFromIntC, "from_int"); exportStrFn(str.strFromIntC, "from_int");
exportStrFn(str.strEqual, "equal");
} }
// Export helpers - Must be run inside a comptime // Export helpers - Must be run inside a comptime

View file

@ -260,6 +260,11 @@ pub const RocStr = extern struct {
} }
}; };
// Str.equal
pub fn strEqual(self: RocStr, other: RocStr) callconv(.C) bool {
return self.eq(other);
}
// Str.numberOfBytes // Str.numberOfBytes
pub fn strNumberOfBytes(string: RocStr) callconv(.C) usize { pub fn strNumberOfBytes(string: RocStr) callconv(.C) usize {
return string.len(); return string.len();

View file

@ -2,14 +2,15 @@ use std::fs::File;
use std::io::prelude::Read; use std::io::prelude::Read;
use std::vec::Vec; use std::vec::Vec;
const PATH: &str = env!(
"BUILTINS_BC",
"Env var BUILTINS_BC not found. Is there a problem with the build script?"
);
pub fn get_bytes() -> Vec<u8> { pub fn get_bytes() -> Vec<u8> {
// In the build script for the builtins module, we compile the builtins bitcode and set // In the build script for the builtins module, we compile the builtins bitcode and set
// BUILTINS_BC to the path to the compiled output. // BUILTINS_BC to the path to the compiled output.
let path: &'static str = env!( let mut builtins_bitcode = File::open(PATH).expect("Unable to find builtins bitcode source");
"BUILTINS_BC",
"Env var BUILTINS_BC not found. Is there a problem with the build script?"
);
let mut builtins_bitcode = File::open(path).expect("Unable to find builtins bitcode source");
let mut buffer = Vec::new(); let mut buffer = Vec::new();
builtins_bitcode builtins_bitcode
.read_to_end(&mut buffer) .read_to_end(&mut buffer)
@ -31,3 +32,4 @@ pub const STR_STARTS_WITH: &str = "roc_builtins.str.starts_with";
pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with"; pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with";
pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes"; pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes";
pub const STR_FROM_INT: &str = "roc_builtins.str.from_int"; pub const STR_FROM_INT: &str = "roc_builtins.str.from_int";
pub const STR_EQUAL: &str = "roc_builtins.str.equal";

View file

@ -3,8 +3,8 @@ use roc_module::ident::TagName;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::Region; use roc_region::all::Region;
use roc_types::builtin_aliases::{ use roc_types::builtin_aliases::{
bool_type, dict_type, float_type, int_type, list_type, num_type, ordering_type, result_type, bool_type, dict_type, float_type, int_type, list_type, nat_type, num_type, ordering_type,
set_type, str_type, result_type, set_type, str_type,
}; };
use roc_types::solved_types::SolvedType; use roc_types::solved_types::SolvedType;
use roc_types::subs::VarId; use roc_types::subs::VarId;
@ -91,10 +91,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// addWrap : Int, Int -> Int // addWrap : Int range, Int range -> Int range
add_type( add_type(
Symbol::NUM_ADD_WRAP, Symbol::NUM_ADD_WRAP,
top_level_function(vec![int_type(), int_type()], Box::new(int_type())), top_level_function(
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR1))),
),
); );
// sub or (-) : Num a, Num a -> Num a // sub or (-) : Num a, Num a -> Num a
@ -106,10 +109,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// subWrap : Int, Int -> Int // subWrap : Int range, Int range -> Int range
add_type( add_type(
Symbol::NUM_SUB_WRAP, Symbol::NUM_SUB_WRAP,
top_level_function(vec![int_type(), int_type()], Box::new(int_type())), top_level_function(
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR1))),
),
); );
// subChecked : Num a, Num a -> Result (Num a) [ Overflow ]* // subChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
@ -130,10 +136,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// mulWrap : Int, Int -> Int // mulWrap : Int range, Int range -> Int range
add_type( add_type(
Symbol::NUM_MUL_WRAP, Symbol::NUM_MUL_WRAP,
top_level_function(vec![int_type(), int_type()], Box::new(int_type())), top_level_function(
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR1))),
),
); );
// mulChecked : Num a, Num a -> Result (Num a) [ Overflow ]* // mulChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
@ -214,10 +223,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// toFloat : Num a -> Float // toFloat : Num * -> Float *
add_type( add_type(
Symbol::NUM_TO_FLOAT, Symbol::NUM_TO_FLOAT,
top_level_function(vec![num_type(flex(TVAR1))], Box::new(float_type())), top_level_function(
vec![num_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR2))),
),
); );
// isNegative : Num a -> Bool // isNegative : Num a -> Bool
@ -250,11 +262,11 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
top_level_function(vec![num_type(flex(TVAR1))], Box::new(bool_type())), top_level_function(vec![num_type(flex(TVAR1))], Box::new(bool_type())),
); );
// maxInt : Int // maxInt : Int range
add_type(Symbol::NUM_MAX_INT, int_type()); add_type(Symbol::NUM_MAX_INT, int_type(flex(TVAR1)));
// minInt : Int // minInt : Int range
add_type(Symbol::NUM_MIN_INT, int_type()); add_type(Symbol::NUM_MIN_INT, int_type(flex(TVAR1)));
// div : Int, Int -> Result Int [ DivByZero ]* // div : Int, Int -> Result Int [ DivByZero ]*
let div_by_zero = SolvedType::TagUnion( let div_by_zero = SolvedType::TagUnion(
@ -265,56 +277,68 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
add_type( add_type(
Symbol::NUM_DIV_INT, Symbol::NUM_DIV_INT,
top_level_function( top_level_function(
vec![int_type(), int_type()], vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(result_type(int_type(), div_by_zero.clone())), Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())),
), ),
); );
// bitwiseAnd : Int, Int -> Int // bitwiseAnd : Int a, Int a -> Int a
add_type( add_type(
Symbol::NUM_BITWISE_AND, Symbol::NUM_BITWISE_AND,
top_level_function(vec![int_type(), int_type()], Box::new(int_type())), top_level_function(
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR1))),
),
); );
// rem : Int, Int -> Result Int [ DivByZero ]* // bitwiseXor : Int a, Int a -> Int a
add_type(
Symbol::NUM_BITWISE_XOR,
top_level_function(
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR1))),
),
);
// rem : Int a, Int a -> Result (Int a) [ DivByZero ]*
add_type( add_type(
Symbol::NUM_REM, Symbol::NUM_REM,
top_level_function( top_level_function(
vec![int_type(), int_type()], vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(result_type(int_type(), div_by_zero.clone())), Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())),
), ),
); );
// mod : Int, Int -> Result Int [ DivByZero ]* // mod : Int a, Int a -> Result (Int a) [ DivByZero ]*
add_type( add_type(
Symbol::NUM_MOD_INT, Symbol::NUM_MOD_INT,
top_level_function( top_level_function(
vec![int_type(), int_type()], vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(result_type(int_type(), div_by_zero.clone())), Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())),
), ),
); );
// Float module // Float module
// div : Float, Float -> Float // div : Float a, Float a -> Float a
add_type( add_type(
Symbol::NUM_DIV_FLOAT, Symbol::NUM_DIV_FLOAT,
top_level_function( top_level_function(
vec![float_type(), float_type()], vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))],
Box::new(result_type(float_type(), div_by_zero.clone())), Box::new(result_type(float_type(flex(TVAR1)), div_by_zero.clone())),
), ),
); );
// mod : Float, Float -> Result Int [ DivByZero ]* // mod : Float a, Float a -> Result (Float a) [ DivByZero ]*
add_type( add_type(
Symbol::NUM_MOD_FLOAT, Symbol::NUM_MOD_FLOAT,
top_level_function( top_level_function(
vec![float_type(), float_type()], vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))],
Box::new(result_type(float_type(), div_by_zero)), Box::new(result_type(float_type(flex(TVAR1)), div_by_zero)),
), ),
); );
// sqrt : Float -> Float // sqrt : Float a -> Float a
let sqrt_of_negative = SolvedType::TagUnion( let sqrt_of_negative = SolvedType::TagUnion(
vec![(TagName::Global("SqrtOfNegative".into()), vec![])], vec![(TagName::Global("SqrtOfNegative".into()), vec![])],
Box::new(SolvedType::Wildcard), Box::new(SolvedType::Wildcard),
@ -323,81 +347,114 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
add_type( add_type(
Symbol::NUM_SQRT, Symbol::NUM_SQRT,
top_level_function( top_level_function(
vec![float_type()], vec![float_type(flex(TVAR1))],
Box::new(result_type(float_type(), sqrt_of_negative)), Box::new(result_type(float_type(flex(TVAR1)), sqrt_of_negative)),
), ),
); );
// round : Float -> Int // round : Float a -> Int b
add_type( add_type(
Symbol::NUM_ROUND, Symbol::NUM_ROUND,
top_level_function(vec![float_type()], Box::new(int_type())), top_level_function(
vec![float_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR2))),
),
); );
// sin : Float -> Float // sin : Float a -> Float a
add_type( add_type(
Symbol::NUM_SIN, Symbol::NUM_SIN,
top_level_function(vec![float_type()], Box::new(float_type())), top_level_function(
vec![float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1))),
),
); );
// cos : Float -> Float // cos : Float a -> Float a
add_type( add_type(
Symbol::NUM_COS, Symbol::NUM_COS,
top_level_function(vec![float_type()], Box::new(float_type())), top_level_function(
vec![float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1))),
),
); );
// tan : Float -> Float // tan : Float a -> Float a
add_type( add_type(
Symbol::NUM_TAN, Symbol::NUM_TAN,
top_level_function(vec![float_type()], Box::new(float_type())), top_level_function(
vec![float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1))),
),
); );
// maxFloat : Float // maxFloat : Float a
add_type(Symbol::NUM_MAX_FLOAT, float_type()); add_type(Symbol::NUM_MAX_FLOAT, float_type(flex(TVAR1)));
// minFloat : Float // minFloat : Float a
add_type(Symbol::NUM_MIN_FLOAT, float_type()); add_type(Symbol::NUM_MIN_FLOAT, float_type(flex(TVAR1)));
// pow : Float, Float -> Float // pow : Float a, Float a -> Float a
add_type( add_type(
Symbol::NUM_POW, Symbol::NUM_POW,
top_level_function(vec![float_type(), float_type()], Box::new(float_type())), top_level_function(
vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1))),
),
); );
// ceiling : Float -> Int // ceiling : Float a -> Int b
add_type( add_type(
Symbol::NUM_CEILING, Symbol::NUM_CEILING,
top_level_function(vec![float_type()], Box::new(int_type())), top_level_function(
vec![float_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR2))),
),
); );
// powInt : Int, Int -> Int // powInt : Int a, Int a -> Int a
add_type( add_type(
Symbol::NUM_POW_INT, Symbol::NUM_POW_INT,
top_level_function(vec![int_type(), int_type()], Box::new(int_type())), top_level_function(
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR1))),
),
); );
// floor : Float -> Int // floor : Float a -> Int b
add_type( add_type(
Symbol::NUM_FLOOR, Symbol::NUM_FLOOR,
top_level_function(vec![float_type()], Box::new(int_type())), top_level_function(
vec![float_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR2))),
),
); );
// atan : Float -> Float // atan : Float a -> Float a
add_type( add_type(
Symbol::NUM_ATAN, Symbol::NUM_ATAN,
top_level_function(vec![float_type()], Box::new(float_type())), top_level_function(
vec![float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1))),
),
); );
// acos : Float -> Float // acos : Float a -> Float a
add_type( add_type(
Symbol::NUM_ACOS, Symbol::NUM_ACOS,
top_level_function(vec![float_type()], Box::new(float_type())), top_level_function(
vec![float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1))),
),
); );
// asin : Float -> Float // asin : Float a -> Float a
add_type( add_type(
Symbol::NUM_ASIN, Symbol::NUM_ASIN,
top_level_function(vec![float_type()], Box::new(float_type())), top_level_function(
vec![float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1))),
),
); );
// Bool module // Bool module
@ -461,21 +518,21 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
top_level_function(vec![str_type(), str_type()], Box::new(bool_type())), top_level_function(vec![str_type(), str_type()], Box::new(bool_type())),
); );
// countGraphemes : Str -> Int // countGraphemes : Str -> Nat
add_type( add_type(
Symbol::STR_COUNT_GRAPHEMES, Symbol::STR_COUNT_GRAPHEMES,
top_level_function(vec![str_type()], Box::new(int_type())), top_level_function(vec![str_type()], Box::new(nat_type())),
); );
// fromInt : Int -> Str // fromInt : Int a -> Str
add_type( add_type(
Symbol::STR_FROM_INT, Symbol::STR_FROM_INT,
top_level_function(vec![int_type()], Box::new(str_type())), top_level_function(vec![int_type(flex(TVAR1))], Box::new(str_type())),
); );
// List module // List module
// get : List elem, Int -> Result elem [ OutOfBounds ]* // get : List elem, Nat -> Result elem [ OutOfBounds ]*
let index_out_of_bounds = SolvedType::TagUnion( let index_out_of_bounds = SolvedType::TagUnion(
vec![(TagName::Global("OutOfBounds".into()), vec![])], vec![(TagName::Global("OutOfBounds".into()), vec![])],
Box::new(SolvedType::Wildcard), Box::new(SolvedType::Wildcard),
@ -484,7 +541,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
add_type( add_type(
Symbol::LIST_GET, Symbol::LIST_GET,
top_level_function( top_level_function(
vec![list_type(flex(TVAR1)), int_type()], vec![list_type(flex(TVAR1)), nat_type()],
Box::new(result_type(flex(TVAR1), index_out_of_bounds)), Box::new(result_type(flex(TVAR1), index_out_of_bounds)),
), ),
); );
@ -512,11 +569,11 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// set : List elem, Int, elem -> List elem // set : List elem, Nat, elem -> List elem
add_type( add_type(
Symbol::LIST_SET, Symbol::LIST_SET,
top_level_function( top_level_function(
vec![list_type(flex(TVAR1)), int_type(), flex(TVAR1)], vec![list_type(flex(TVAR1)), nat_type(), flex(TVAR1)],
Box::new(list_type(flex(TVAR1))), Box::new(list_type(flex(TVAR1))),
), ),
); );
@ -631,11 +688,11 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
top_level_function(vec![flex(TVAR1)], Box::new(list_type(flex(TVAR1)))), top_level_function(vec![flex(TVAR1)], Box::new(list_type(flex(TVAR1)))),
); );
// repeat : Int, elem -> List elem // repeat : Nat, elem -> List elem
add_type( add_type(
Symbol::LIST_REPEAT, Symbol::LIST_REPEAT,
top_level_function( top_level_function(
vec![int_type(), flex(TVAR1)], vec![nat_type(), flex(TVAR2)],
Box::new(list_type(flex(TVAR1))), Box::new(list_type(flex(TVAR1))),
), ),
); );
@ -649,10 +706,10 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// len : List * -> Int // len : List * -> Nat
add_type( add_type(
Symbol::LIST_LEN, Symbol::LIST_LEN,
top_level_function(vec![list_type(flex(TVAR1))], Box::new(int_type())), top_level_function(vec![list_type(flex(TVAR1))], Box::new(nat_type())),
); );
// isEmpty : List * -> Bool // isEmpty : List * -> Bool

View file

@ -166,8 +166,8 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// addWrap : Int, Int -> Int // addWrap : Int, Int -> Int
add_type(Symbol::NUM_ADD_WRAP, { add_type(Symbol::NUM_ADD_WRAP, {
let_tvars! { u, v, w }; let_tvars! { u, v, w, int };
unique_function(vec![int_type(u), int_type(v)], int_type(w)) unique_function(vec![int_type(u, int), int_type(v, int)], int_type(w, int))
}); });
// sub or (-) : Num a, Num a -> Num a // sub or (-) : Num a, Num a -> Num a
@ -199,8 +199,8 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// mulWrap : Int, Int -> Int // mulWrap : Int, Int -> Int
add_type(Symbol::NUM_MUL_WRAP, { add_type(Symbol::NUM_MUL_WRAP, {
let_tvars! { u, v, w }; let_tvars! { u, v, w , int };
unique_function(vec![int_type(u), int_type(v)], int_type(w)) unique_function(vec![int_type(u, int), int_type(v, int)], int_type(w, int))
}); });
// mulChecked : Num a, Num a -> Result (Num a) [ Overflow ]* // mulChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
@ -257,38 +257,50 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// rem : Attr * Int, Attr * Int -> Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*)) // rem : Attr * Int, Attr * Int -> Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*))
add_type(Symbol::NUM_REM, { add_type(Symbol::NUM_REM, {
let_tvars! { star1, star2, star3, star4, star5 }; let_tvars! { star1, star2, star3, star4, star5, int };
unique_function( unique_function(
vec![int_type(star1), int_type(star2)], vec![int_type(star1, int), int_type(star2, int)],
result_type(star3, int_type(star4), lift(star5, div_by_zero())), result_type(star3, int_type(star4, int), lift(star5, div_by_zero())),
) )
}); });
// maxInt : Int // maxInt : Int
add_type(Symbol::NUM_MAX_INT, { add_type(Symbol::NUM_MAX_INT, {
let_tvars! { star }; let_tvars! { star, int };
int_type(star) int_type(star, int)
}); });
// minInt : Int // minInt : Int
add_type(Symbol::NUM_MIN_INT, { add_type(Symbol::NUM_MIN_INT, {
let_tvars! { star }; let_tvars! { star, int };
int_type(star) int_type(star, int)
}); });
// divFloor or (//) : Int, Int -> Result Int [ DivByZero ]* // divFloor or (//) : Int, Int -> Result Int [ DivByZero ]*
add_type(Symbol::NUM_DIV_INT, { add_type(Symbol::NUM_DIV_INT, {
let_tvars! { star1, star2, star3, star4, star5 }; let_tvars! { star1, star2, star3, star4, star5, int };
unique_function( unique_function(
vec![int_type(star1), int_type(star2)], vec![int_type(star1, int), int_type(star2, int)],
result_type(star3, int_type(star4), lift(star5, div_by_zero())), result_type(star3, int_type(star4, int), lift(star5, div_by_zero())),
) )
}); });
// bitwiseAnd : Attr * Int, Attr * Int -> Attr * Int // bitwiseAnd : Attr * Int, Attr * Int -> Attr * Int
add_type(Symbol::NUM_BITWISE_AND, { add_type(Symbol::NUM_BITWISE_AND, {
let_tvars! { star1, star2, star3 }; let_tvars! { star1, star2, star3, int };
unique_function(vec![int_type(star1), int_type(star2)], int_type(star3)) unique_function(
vec![int_type(star1, int), int_type(star2, int)],
int_type(star3, int),
)
});
// bitwiseAnd : Attr * Int, Attr * Int -> Attr * Int
add_type(Symbol::NUM_BITWISE_XOR, {
let_tvars! { star1, star2, star3, int };
unique_function(
vec![int_type(star1, int), int_type(star2, int)],
int_type(star3, int),
)
}); });
// divFloat : Float, Float -> Float // divFloat : Float, Float -> Float
@ -302,8 +314,8 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// round : Float -> Int // round : Float -> Int
add_type(Symbol::NUM_ROUND, { add_type(Symbol::NUM_ROUND, {
let_tvars! { star1, star2 }; let_tvars! { star1, star2, int };
unique_function(vec![float_type(star1)], int_type(star2)) unique_function(vec![float_type(star1)], int_type(star2, int))
}); });
// sqrt : Float -> Float // sqrt : Float -> Float
@ -391,20 +403,23 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// ceiling : Float -> Int // ceiling : Float -> Int
add_type(Symbol::NUM_CEILING, { add_type(Symbol::NUM_CEILING, {
let_tvars! { star1, star2 }; let_tvars! { star1, star2, int };
unique_function(vec![float_type(star1)], int_type(star2)) unique_function(vec![float_type(star1)], int_type(star2, int))
}); });
// powInt : Int, Int -> Int // powInt : Int, Int -> Int
add_type(Symbol::NUM_POW_INT, { add_type(Symbol::NUM_POW_INT, {
let_tvars! { star1, star2, star3 }; let_tvars! { star1, star2, star3 , int };
unique_function(vec![int_type(star1), int_type(star2)], int_type(star3)) unique_function(
vec![int_type(star1, int), int_type(star2, int)],
int_type(star3, int),
)
}); });
// floor : Float -> Int // floor : Float -> Int
add_type(Symbol::NUM_FLOOR, { add_type(Symbol::NUM_FLOOR, {
let_tvars! { star1, star2 }; let_tvars! { star1, star2 , int};
unique_function(vec![float_type(star1)], int_type(star2)) unique_function(vec![float_type(star1)], int_type(star2, int))
}); });
// atan : Float -> Float // atan : Float -> Float
@ -479,8 +494,8 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// len : Attr * (List *) -> Attr * Int // len : Attr * (List *) -> Attr * Int
add_type(Symbol::LIST_LEN, { add_type(Symbol::LIST_LEN, {
let_tvars! { star1, a, star2 }; let_tvars! { star1, a, star2 , int };
unique_function(vec![list_type(star1, a)], int_type(star2)) unique_function(vec![list_type(star1, a)], int_type(star2, int))
}); });
fn list_was_empty() -> SolvedType { fn list_was_empty() -> SolvedType {
@ -536,7 +551,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
); );
add_type(Symbol::LIST_GET, { add_type(Symbol::LIST_GET, {
let_tvars! { a, u, star1, star2, star3, star4 }; let_tvars! { a, u, star1, star2, star3, star4, int};
unique_function( unique_function(
vec![ vec![
@ -547,7 +562,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
SolvedType::Apply(Symbol::LIST_LIST, vec![attr_type(u, a)]), SolvedType::Apply(Symbol::LIST_LIST, vec![attr_type(u, a)]),
], ],
), ),
int_type(star2), int_type(star2, int),
], ],
result_type(star3, attr_type(u, a), lift(star4, index_out_of_bounds)), result_type(star3, attr_type(u, a), lift(star4, index_out_of_bounds)),
) )
@ -559,7 +574,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// Attr (u | v) a // Attr (u | v) a
// -> Attr * (List (Attr u a)) // -> Attr * (List (Attr u a))
add_type(Symbol::LIST_SET, { add_type(Symbol::LIST_SET, {
let_tvars! { u, v, w, star1, star2, a }; let_tvars! { u, v, w, star1, star2, a, int};
unique_function( unique_function(
vec![ vec![
@ -570,7 +585,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
SolvedType::Apply(Symbol::LIST_LIST, vec![attr_type(u, a)]), SolvedType::Apply(Symbol::LIST_LIST, vec![attr_type(u, a)]),
], ],
), ),
int_type(star1), int_type(star1, int),
SolvedType::Apply(Symbol::ATTR_ATTR, vec![container(u, vec![v]), flex(a)]), SolvedType::Apply(Symbol::ATTR_ATTR, vec![container(u, vec![v]), flex(a)]),
], ],
SolvedType::Apply( SolvedType::Apply(
@ -627,10 +642,10 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// , Attr Shared a // , Attr Shared a
// -> Attr * (List (Attr Shared a)) // -> Attr * (List (Attr Shared a))
add_type(Symbol::LIST_REPEAT, { add_type(Symbol::LIST_REPEAT, {
let_tvars! { a, star1, star2 }; let_tvars! { a, star1, star2, int };
unique_function( unique_function(
vec![int_type(star1), shared(flex(a))], vec![int_type(star1, int), shared(flex(a))],
SolvedType::Apply( SolvedType::Apply(
Symbol::ATTR_ATTR, Symbol::ATTR_ATTR,
vec![ vec![
@ -1162,14 +1177,14 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// Str.countGraphemes : Attr * Str, -> Attr * Int // Str.countGraphemes : Attr * Str, -> Attr * Int
add_type(Symbol::STR_COUNT_GRAPHEMES, { add_type(Symbol::STR_COUNT_GRAPHEMES, {
let_tvars! { star1, star2 }; let_tvars! { star1, star2, int };
unique_function(vec![str_type(star1)], int_type(star2)) unique_function(vec![str_type(star1)], int_type(star2, int))
}); });
// fromInt : Attr * Int -> Attr * Str // fromInt : Attr * Int -> Attr * Str
add_type(Symbol::STR_FROM_INT, { add_type(Symbol::STR_FROM_INT, {
let_tvars! { star1, star2 }; let_tvars! { star1, star2, int };
unique_function(vec![int_type(star1)], str_type(star2)) unique_function(vec![int_type(star1, int)], str_type(star2))
}); });
// Result module // Result module
@ -1242,9 +1257,8 @@ fn lift(u: VarId, a: SolvedType) -> SolvedType {
#[inline(always)] #[inline(always)]
fn float_type(u: VarId) -> SolvedType { fn float_type(u: VarId) -> SolvedType {
let b_64 = builtin_aliases::binary64_type(); let inner_type = lift(u, flex(u));
let attr_b_64 = lift(u, b_64); let fp = builtin_aliases::floatingpoint_type(inner_type.clone());
let fp = builtin_aliases::floatingpoint_type(attr_b_64);
let attr_fb = lift(u, fp); let attr_fb = lift(u, fp);
let num = builtin_aliases::num_type(attr_fb); let num = builtin_aliases::num_type(attr_fb);
@ -1252,16 +1266,19 @@ fn float_type(u: VarId) -> SolvedType {
Symbol::ATTR_ATTR, Symbol::ATTR_ATTR,
vec![ vec![
flex(u), flex(u),
SolvedType::Alias(Symbol::NUM_F64, Vec::new(), Box::new(num)), SolvedType::Alias(
Symbol::NUM_FLOAT,
vec![("range".into(), inner_type)],
Box::new(num),
),
], ],
) )
} }
#[inline(always)] #[inline(always)]
fn int_type(u: VarId) -> SolvedType { fn int_type(u: VarId, range: VarId) -> SolvedType {
let signed_64 = builtin_aliases::signed64_type(); let inner_type = lift(u, flex(range));
let attr_signed_64 = lift(u, signed_64); let integer = builtin_aliases::integer_type(inner_type.clone());
let integer = builtin_aliases::integer_type(attr_signed_64);
let attr_fb = lift(u, integer); let attr_fb = lift(u, integer);
let num = builtin_aliases::num_type(attr_fb); let num = builtin_aliases::num_type(attr_fb);
@ -1269,7 +1286,11 @@ fn int_type(u: VarId) -> SolvedType {
Symbol::ATTR_ATTR, Symbol::ATTR_ATTR,
vec![ vec![
flex(u), flex(u),
SolvedType::Alias(Symbol::NUM_I64, Vec::new(), Box::new(num)), SolvedType::Alias(
Symbol::NUM_INT,
vec![("range".into(), inner_type)],
Box::new(num),
),
], ],
) )
} }

View file

@ -117,7 +117,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_ASIN => num_asin, NUM_ASIN => num_asin,
NUM_MAX_INT => num_max_int, NUM_MAX_INT => num_max_int,
NUM_MIN_INT => num_min_int, NUM_MIN_INT => num_min_int,
NUM_BITWISE_AND => num_bitwise_and NUM_BITWISE_AND => num_bitwise_and,
NUM_BITWISE_XOR => num_bitwise_xor
} }
} }
@ -212,7 +213,8 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
/// Num.maxInt : Int /// Num.maxInt : Int
fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh(); let int_var = var_store.fresh();
let body = Int(int_var, i64::MAX); let int_percision_var = var_store.fresh();
let body = Int(int_var, int_percision_var, i64::MAX);
Def { Def {
annotation: None, annotation: None,
@ -226,7 +228,8 @@ fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
/// Num.minInt : Int /// Num.minInt : Int
fn num_min_int(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_min_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh(); let int_var = var_store.fresh();
let body = Int(int_var, i64::MIN); let int_percision_var = var_store.fresh();
let body = Int(int_var, int_percision_var, i64::MIN);
Def { Def {
annotation: None, annotation: None,
@ -846,7 +849,7 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def {
let body = RunLowLevel { let body = RunLowLevel {
op: LowLevel::Eq, op: LowLevel::Eq,
args: vec![ args: vec![
(arg_var, Int(var_store.fresh(), 1)), (arg_var, Int(var_store.fresh(), var_store.fresh(), 1)),
( (
arg_var, arg_var,
RunLowLevel { RunLowLevel {
@ -930,6 +933,7 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh(); let bool_var = var_store.fresh();
let float_var = var_store.fresh(); let float_var = var_store.fresh();
let unbound_zero_var = var_store.fresh(); let unbound_zero_var = var_store.fresh();
let percision_var = var_store.fresh();
let ret_var = var_store.fresh(); let ret_var = var_store.fresh();
let body = If { let body = If {
@ -943,7 +947,7 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(float_var, Var(Symbol::ARG_1)), (float_var, Var(Symbol::ARG_1)),
(float_var, Float(unbound_zero_var, 0.0)), (float_var, Float(unbound_zero_var, percision_var, 0.0)),
], ],
ret_var: bool_var, ret_var: bool_var,
}, },
@ -1150,6 +1154,11 @@ fn num_bitwise_and(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumBitwiseAnd) num_binop(symbol, var_store, LowLevel::NumBitwiseAnd)
} }
/// Num.bitwiseXor : Int, Int -> Int
fn num_bitwise_xor(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumBitwiseXor)
}
/// List.isEmpty : List * -> Bool /// List.isEmpty : List * -> Bool
fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();
@ -1896,6 +1905,7 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh(); let bool_var = var_store.fresh();
let num_var = var_store.fresh(); let num_var = var_store.fresh();
let unbound_zero_var = var_store.fresh(); let unbound_zero_var = var_store.fresh();
let percision_var = var_store.fresh();
let ret_var = var_store.fresh(); let ret_var = var_store.fresh();
let body = If { let body = If {
@ -1909,7 +1919,7 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(num_var, Var(Symbol::ARG_2)), (num_var, Var(Symbol::ARG_2)),
(num_var, Float(unbound_zero_var, 0.0)), (num_var, Float(unbound_zero_var, percision_var, 0.0)),
], ],
ret_var: bool_var, ret_var: bool_var,
}, },
@ -1958,6 +1968,7 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh(); let bool_var = var_store.fresh();
let num_var = var_store.fresh(); let num_var = var_store.fresh();
let unbound_zero_var = var_store.fresh(); let unbound_zero_var = var_store.fresh();
let unbound_zero_percision_var = var_store.fresh();
let ret_var = var_store.fresh(); let ret_var = var_store.fresh();
let body = If { let body = If {
@ -1971,7 +1982,10 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(num_var, Var(Symbol::ARG_2)), (num_var, Var(Symbol::ARG_2)),
(num_var, Int(unbound_zero_var, 0)), (
num_var,
Int(unbound_zero_var, unbound_zero_percision_var, 0),
),
], ],
ret_var: bool_var, ret_var: bool_var,
}, },
@ -2025,6 +2039,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();
let len_var = var_store.fresh(); let len_var = var_store.fresh();
let zero_var = var_store.fresh(); let zero_var = var_store.fresh();
let zero_percision_var = var_store.fresh();
let list_elem_var = var_store.fresh(); let list_elem_var = var_store.fresh();
let ret_var = var_store.fresh(); let ret_var = var_store.fresh();
@ -2039,7 +2054,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel { RunLowLevel {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(len_var, Int(zero_var, 0)), (len_var, Int(zero_var, zero_percision_var, 0)),
( (
len_var, len_var,
RunLowLevel { RunLowLevel {
@ -2061,7 +2076,10 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
// List.#getUnsafe list 0 // List.#getUnsafe list 0
RunLowLevel { RunLowLevel {
op: LowLevel::ListGetUnsafe, op: LowLevel::ListGetUnsafe,
args: vec![(list_var, Var(Symbol::ARG_1)), (len_var, Int(zero_var, 0))], args: vec![
(list_var, Var(Symbol::ARG_1)),
(len_var, Int(zero_var, zero_percision_var, 0)),
],
ret_var: list_elem_var, ret_var: list_elem_var,
}, },
], ],
@ -2102,6 +2120,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();
let len_var = var_store.fresh(); let len_var = var_store.fresh();
let num_var = var_store.fresh(); let num_var = var_store.fresh();
let num_percision_var = var_store.fresh();
let list_elem_var = var_store.fresh(); let list_elem_var = var_store.fresh();
let ret_var = var_store.fresh(); let ret_var = var_store.fresh();
@ -2116,7 +2135,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel { RunLowLevel {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(len_var, Int(num_var, 0)), (len_var, Int(num_var, num_percision_var, 0)),
( (
len_var, len_var,
RunLowLevel { RunLowLevel {
@ -2155,7 +2174,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
ret_var: len_var, ret_var: len_var,
}, },
), ),
(arg_var, Int(num_var, 1)), (arg_var, Int(num_var, num_percision_var, 1)),
], ],
ret_var: len_var, ret_var: len_var,
}, },

View file

@ -735,8 +735,8 @@ fn pattern_to_vars_by_symbol(
} }
NumLiteral(_, _) NumLiteral(_, _)
| IntLiteral(_) | IntLiteral(_, _)
| FloatLiteral(_) | FloatLiteral(_, _)
| StrLiteral(_) | StrLiteral(_)
| Underscore | Underscore
| MalformedPattern(_, _) | MalformedPattern(_, _)
@ -864,7 +864,9 @@ fn canonicalize_pending_def<'a>(
} }
} }
Alias { name, ann, vars } => { Alias {
name, ann, vars, ..
} => {
let symbol = name.value; let symbol = name.value;
let can_ann = canonicalize_annotation(env, scope, &ann.value, ann.region, var_store); let can_ann = canonicalize_annotation(env, scope, &ann.value, ann.region, var_store);
@ -1407,7 +1409,9 @@ fn to_pending_def<'a>(
} }
} }
Alias { name, vars, ann } => { Alias {
name, vars, ann, ..
} => {
let region = Region::span_across(&name.region, &ann.region); let region = Region::span_across(&name.region, &ann.region);
match scope.introduce( match scope.introduce(

View file

@ -56,8 +56,8 @@ pub enum Expr {
Num(Variable, i64), Num(Variable, i64),
// Int and Float store a variable to generate better error messages // Int and Float store a variable to generate better error messages
Int(Variable, i64), Int(Variable, Variable, i64),
Float(Variable, f64), Float(Variable, Variable, f64),
Str(InlinableString), Str(InlinableString),
List { List {
list_var: Variable, // required for uniqueness of the list list_var: Variable, // required for uniqueness of the list
@ -1168,8 +1168,8 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
// Num stores the `a` variable in `Num a`. Not the same as the variable // Num stores the `a` variable in `Num a`. Not the same as the variable
// stored in Int and Float below, which is strictly for better error messages // stored in Int and Float below, which is strictly for better error messages
other @ Num(_, _) other @ Num(_, _)
| other @ Int(_, _) | other @ Int(_, _, _)
| other @ Float(_, _) | other @ Float(_, _, _)
| other @ Str { .. } | other @ Str { .. }
| other @ RuntimeError(_) | other @ RuntimeError(_)
| other @ EmptyRecord | other @ EmptyRecord

View file

@ -363,8 +363,8 @@ fn fix_values_captured_in_closure_pattern(
} }
Identifier(_) Identifier(_)
| NumLiteral(_, _) | NumLiteral(_, _)
| IntLiteral(_) | IntLiteral(_, _)
| FloatLiteral(_) | FloatLiteral(_, _)
| StrLiteral(_) | StrLiteral(_)
| Underscore | Underscore
| Shadowed(_, _) | Shadowed(_, _)
@ -414,8 +414,8 @@ fn fix_values_captured_in_closure_expr(
} }
Num(_, _) Num(_, _)
| Int(_, _) | Int(_, _, _)
| Float(_, _) | Float(_, _, _)
| Str(_) | Str(_)
| Var(_) | Var(_)
| EmptyRecord | EmptyRecord

View file

@ -45,7 +45,7 @@ pub fn int_expr_from_result(
) -> Expr { ) -> Expr {
// Int stores a variable to generate better error messages // Int stores a variable to generate better error messages
match result { match result {
Ok(int) => Expr::Int(var_store.fresh(), int), Ok(int) => Expr::Int(var_store.fresh(), var_store.fresh(), int),
Err((raw, error)) => { Err((raw, error)) => {
let runtime_error = InvalidInt(error, base, region, raw.into()); let runtime_error = InvalidInt(error, base, region, raw.into());
@ -65,7 +65,7 @@ pub fn float_expr_from_result(
) -> Expr { ) -> Expr {
// Float stores a variable to generate better error messages // Float stores a variable to generate better error messages
match result { match result {
Ok(float) => Expr::Float(var_store.fresh(), float), Ok(float) => Expr::Float(var_store.fresh(), var_store.fresh(), float),
Err((raw, error)) => { Err((raw, error)) => {
let runtime_error = InvalidFloat(error, region, raw.into()); let runtime_error = InvalidFloat(error, region, raw.into());

View file

@ -25,9 +25,9 @@ pub enum Pattern {
ext_var: Variable, ext_var: Variable,
destructs: Vec<Located<RecordDestruct>>, destructs: Vec<Located<RecordDestruct>>,
}, },
IntLiteral(i64), IntLiteral(Variable, i64),
NumLiteral(Variable, i64), NumLiteral(Variable, i64),
FloatLiteral(f64), FloatLiteral(Variable, f64),
StrLiteral(Box<str>), StrLiteral(Box<str>),
Underscore, Underscore,
@ -86,8 +86,8 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
} }
NumLiteral(_, _) NumLiteral(_, _)
| IntLiteral(_) | IntLiteral(_, _)
| FloatLiteral(_) | FloatLiteral(_, _)
| StrLiteral(_) | StrLiteral(_)
| Underscore | Underscore
| MalformedPattern(_, _) | MalformedPattern(_, _)
@ -191,7 +191,7 @@ pub fn canonicalize_pattern<'a>(
let problem = MalformedPatternProblem::MalformedFloat; let problem = MalformedPatternProblem::MalformedFloat;
malformed_pattern(env, problem, region) malformed_pattern(env, problem, region)
} }
Ok(float) => Pattern::FloatLiteral(float), Ok(float) => Pattern::FloatLiteral(var_store.fresh(), float),
}, },
ptype => unsupported_pattern(env, ptype, region), ptype => unsupported_pattern(env, ptype, region),
}, },
@ -224,9 +224,9 @@ pub fn canonicalize_pattern<'a>(
} }
Ok(int) => { Ok(int) => {
if *is_negative { if *is_negative {
Pattern::IntLiteral(-int) Pattern::IntLiteral(var_store.fresh(), -int)
} else { } else {
Pattern::IntLiteral(int) Pattern::IntLiteral(var_store.fresh(), int)
} }
} }
}, },
@ -455,8 +455,8 @@ fn add_bindings_from_patterns(
} }
} }
NumLiteral(_, _) NumLiteral(_, _)
| IntLiteral(_) | IntLiteral(_, _)
| FloatLiteral(_) | FloatLiteral(_, _)
| StrLiteral(_) | StrLiteral(_)
| Underscore | Underscore
| Shadowed(_, _) | Shadowed(_, _)

View file

@ -32,7 +32,7 @@ mod test_can {
let actual_out = can_expr_with(&arena, test_home(), input); let actual_out = can_expr_with(&arena, test_home(), input);
match actual_out.loc_expr.value { match actual_out.loc_expr.value {
Expr::Float(_, actual) => { Expr::Float(_, _, actual) => {
assert_eq!(expected, actual); assert_eq!(expected, actual);
} }
actual => { actual => {
@ -46,11 +46,11 @@ mod test_can {
let actual_out = can_expr_with(&arena, test_home(), input); let actual_out = can_expr_with(&arena, test_home(), input);
match actual_out.loc_expr.value { match actual_out.loc_expr.value {
Expr::Int(_, actual) => { Expr::Int(_, _, actual) => {
assert_eq!(expected, actual); assert_eq!(expected, actual);
} }
actual => { actual => {
panic!("Expected an I64, but got: {:?}", actual); panic!("Expected an Int *, but got: {:?}", actual);
} }
} }
} }
@ -249,7 +249,7 @@ mod test_can {
fn correct_annotated_body() { fn correct_annotated_body() {
let src = indoc!( let src = indoc!(
r#" r#"
f : I64 -> I64 f : Int * -> Int *
f = \ a -> a f = \ a -> a
f f
@ -265,7 +265,7 @@ mod test_can {
fn correct_annotated_body_with_comments() { fn correct_annotated_body_with_comments() {
let src = indoc!( let src = indoc!(
r#" r#"
f : I64 -> I64 # comment f : Int * -> Int * # comment
f = \ a -> a f = \ a -> a
f f
@ -281,7 +281,7 @@ mod test_can {
fn name_mismatch_annotated_body() { fn name_mismatch_annotated_body() {
let src = indoc!( let src = indoc!(
r#" r#"
f : I64 -> I64 f : Int * -> Int *
g = \ a -> a g = \ a -> a
g g
@ -307,7 +307,7 @@ mod test_can {
fn name_mismatch_annotated_body_with_comment() { fn name_mismatch_annotated_body_with_comment() {
let src = indoc!( let src = indoc!(
r#" r#"
f : I64 -> I64 # comment f : Int * -> Int * # comment
g = \ a -> a g = \ a -> a
g g
@ -333,7 +333,7 @@ mod test_can {
fn separated_annotated_body() { fn separated_annotated_body() {
let src = indoc!( let src = indoc!(
r#" r#"
f : I64 -> I64 f : Int * -> Int *
f = \ a -> a f = \ a -> a
@ -354,7 +354,7 @@ mod test_can {
fn separated_annotated_body_with_comment() { fn separated_annotated_body_with_comment() {
let src = indoc!( let src = indoc!(
r#" r#"
f : I64 -> I64 f : Int * -> Int *
# comment # comment
f = \ a -> a f = \ a -> a
@ -375,9 +375,9 @@ mod test_can {
fn shadowed_annotation() { fn shadowed_annotation() {
let src = indoc!( let src = indoc!(
r#" r#"
f : I64 -> I64 f : Int * -> Int *
f : I64 -> I64 f : Int * -> Int *
f f
"# "#
@ -397,7 +397,7 @@ mod test_can {
fn correct_nested_unannotated_body() { fn correct_nested_unannotated_body() {
let src = indoc!( let src = indoc!(
r#" r#"
f : I64 f : Int *
f = f =
g = 42 g = 42
@ -416,9 +416,9 @@ mod test_can {
fn correct_nested_annotated_body() { fn correct_nested_annotated_body() {
let src = indoc!( let src = indoc!(
r#" r#"
f : I64 f : Int *
f = f =
g : I64 g : Int *
g = 42 g = 42
g + 1 g + 1
@ -436,11 +436,11 @@ mod test_can {
fn correct_nested_body_annotated_multiple_lines() { fn correct_nested_body_annotated_multiple_lines() {
let src = indoc!( let src = indoc!(
r#" r#"
f : I64 f : Int *
f = f =
g : I64 g : Int *
g = 42 g = 42
h : I64 h : Int *
h = 5 h = 5
z = 4 z = 4
g + h + z g + h + z
@ -458,10 +458,10 @@ mod test_can {
fn correct_nested_body_unannotated_multiple_lines() { fn correct_nested_body_unannotated_multiple_lines() {
let src = indoc!( let src = indoc!(
r#" r#"
f : I64 f : Int *
f = f =
g = 42 g = 42
h : I64 h : Int *
h = 5 h = 5
z = 4 z = 4
g + h + z g + h + z
@ -478,7 +478,7 @@ mod test_can {
fn correct_double_nested_body() { fn correct_double_nested_body() {
let src = indoc!( let src = indoc!(
r#" r#"
f : I64 f : Int *
f = f =
g = g =
h = 42 h = 42
@ -499,7 +499,7 @@ mod test_can {
fn annotation_followed_with_unrelated_affectation() { fn annotation_followed_with_unrelated_affectation() {
let src = indoc!( let src = indoc!(
r#" r#"
F : I64 F : Int *
x = 1 x = 1
@ -520,9 +520,9 @@ mod test_can {
fn two_annotations_followed_with_unrelated_affectation() { fn two_annotations_followed_with_unrelated_affectation() {
let src = indoc!( let src = indoc!(
r#" r#"
G : I64 G : Int *
F : I64 F : Int *
x = 1 x = 1

View file

@ -11,30 +11,48 @@ use roc_types::types::Reason;
use roc_types::types::Type::{self, *}; use roc_types::types::Type::{self, *};
#[inline(always)] #[inline(always)]
pub fn int_literal(num_var: Variable, expected: Expected<Type>, region: Region) -> Constraint { pub fn int_literal(
num_var: Variable,
percision_var: Variable,
expected: Expected<Type>,
region: Region,
) -> Constraint {
let num_type = Variable(num_var); let num_type = Variable(num_var);
let reason = Reason::IntLiteral; let reason = Reason::IntLiteral;
let expected_literal = ForReason(reason, num_int(), region);
exists( exists(
vec![num_var], vec![num_var],
And(vec![ And(vec![
Eq(num_type.clone(), expected_literal, Category::Int, region), Eq(
num_type.clone(),
ForReason(reason, num_int(Type::Variable(percision_var)), region),
Category::Int,
region,
),
Eq(num_type, expected, Category::Int, region), Eq(num_type, expected, Category::Int, region),
]), ]),
) )
} }
#[inline(always)] #[inline(always)]
pub fn float_literal(num_var: Variable, expected: Expected<Type>, region: Region) -> Constraint { pub fn float_literal(
num_var: Variable,
percision_var: Variable,
expected: Expected<Type>,
region: Region,
) -> Constraint {
let num_type = Variable(num_var); let num_type = Variable(num_var);
let reason = Reason::FloatLiteral; let reason = Reason::FloatLiteral;
let expected_literal = ForReason(reason, num_float(), region);
exists( exists(
vec![num_var], vec![num_var],
And(vec![ And(vec![
Eq(num_type.clone(), expected_literal, Category::Float, region), Eq(
num_type.clone(),
ForReason(reason, num_float(Type::Variable(percision_var)), region),
Category::Float,
region,
),
Eq(num_type, expected, Category::Float, region), Eq(num_type, expected, Category::Float, region),
]), ]),
) )
@ -72,11 +90,11 @@ pub fn str_type() -> Type {
} }
#[inline(always)] #[inline(always)]
pub fn num_float() -> Type { pub fn num_float(range: Type) -> Type {
Type::Alias( Type::Alias(
Symbol::NUM_F64, Symbol::NUM_FLOAT,
vec![], vec![("range".into(), range.clone())],
Box::new(num_num(num_floatingpoint(num_binary64()))), Box::new(num_num(num_floatingpoint(range))),
) )
} }
@ -108,11 +126,11 @@ pub fn num_binary64() -> Type {
} }
#[inline(always)] #[inline(always)]
pub fn num_int() -> Type { pub fn num_int(range: Type) -> Type {
Type::Alias( Type::Alias(
Symbol::NUM_I64, Symbol::NUM_INT,
vec![], vec![("range".into(), range.clone())],
Box::new(num_num(num_integer(num_signed64()))), Box::new(num_num(num_integer(range))),
) )
} }

View file

@ -96,7 +96,7 @@ pub fn constrain_expr(
expected: Expected<Type>, expected: Expected<Type>,
) -> Constraint { ) -> Constraint {
match expr { match expr {
Int(var, _) => int_literal(*var, expected, region), Int(var, percision, _) => int_literal(*var, *percision, expected, region),
Num(var, _) => exists( Num(var, _) => exists(
vec![*var], vec![*var],
Eq( Eq(
@ -106,7 +106,7 @@ pub fn constrain_expr(
region, region,
), ),
), ),
Float(var, _) => float_literal(*var, expected, region), Float(var, percision, _) => float_literal(*var, *percision, expected, region),
EmptyRecord => constrain_empty_record(region, expected), EmptyRecord => constrain_empty_record(region, expected),
Expr::Record { record_var, fields } => { Expr::Record { record_var, fields } => {
if fields.is_empty() { if fields.is_empty() {

View file

@ -57,8 +57,8 @@ fn headers_from_annotation_help(
| MalformedPattern(_, _) | MalformedPattern(_, _)
| UnsupportedPattern(_) | UnsupportedPattern(_)
| NumLiteral(_, _) | NumLiteral(_, _)
| IntLiteral(_) | IntLiteral(_, _)
| FloatLiteral(_) | FloatLiteral(_, _)
| StrLiteral(_) => true, | StrLiteral(_) => true,
RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() { RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() {
@ -154,20 +154,20 @@ pub fn constrain_pattern(
)); ));
} }
IntLiteral(_) => { IntLiteral(precision_var, _) => {
state.constraints.push(Constraint::Pattern( state.constraints.push(Constraint::Pattern(
region, region,
PatternCategory::Int, PatternCategory::Int,
builtins::num_int(), builtins::num_int(Type::Variable(*precision_var)),
expected, expected,
)); ));
} }
FloatLiteral(_) => { FloatLiteral(precision_var, _) => {
state.constraints.push(Constraint::Pattern( state.constraints.push(Constraint::Pattern(
region, region,
PatternCategory::Float, PatternCategory::Float,
builtins::num_float(), builtins::num_float(Type::Variable(*precision_var)),
expected, expected,
)); ));
} }

View file

@ -1,4 +1,4 @@
use crate::builtins::{num_binary64, num_floatingpoint, num_integer, num_num, num_signed64}; use crate::builtins::{num_floatingpoint, num_integer, num_num};
use crate::expr::{exists, Info}; use crate::expr::{exists, Info};
use roc_can::annotation::IntroducedVariables; use roc_can::annotation::IntroducedVariables;
use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::Constraint::{self, *};
@ -173,18 +173,19 @@ fn constrain_pattern(
)); ));
} }
IntLiteral(_) => { IntLiteral(inner_var, _) => {
let (a, b, c, num_type) = unique_int(var_store); let (a, b, c, num_type) = unique_int(var_store);
state.constraints.push(exists( state.constraints.push(exists(
vec![a, b, c], vec![*inner_var, a, b, c],
Constraint::Pattern(pattern.region, PatternCategory::Int, num_type, expected), Constraint::Pattern(pattern.region, PatternCategory::Int, num_type, expected),
)); ));
} }
FloatLiteral(_) => { FloatLiteral(inner_var, _) => {
let (a, b, c, num_type) = unique_float(var_store); let (a, b, c, num_type) = unique_float(var_store);
state.constraints.push(exists( state.constraints.push(exists(
vec![a, b, c], vec![*inner_var, a, b, c],
Constraint::Pattern(pattern.region, PatternCategory::Float, num_type, expected), Constraint::Pattern(pattern.region, PatternCategory::Float, num_type, expected),
)); ));
} }
@ -423,10 +424,9 @@ fn unique_int(var_store: &mut VarStore) -> (Variable, Variable, Variable, Type)
let num_uvar1 = var_store.fresh(); let num_uvar1 = var_store.fresh();
let num_uvar2 = var_store.fresh(); let num_uvar2 = var_store.fresh();
let num_uvar3 = var_store.fresh(); let num_uvar3 = var_store.fresh();
let num_uvar4 = var_store.fresh();
let signed_64 = num_signed64(); let integer = num_integer(Type::Variable(num_uvar4));
let attr_signed_64 = attr_type(Bool::variable(num_uvar1), signed_64);
let integer = num_integer(attr_signed_64);
let attr_int = attr_type(Bool::variable(num_uvar2), integer); let attr_int = attr_type(Bool::variable(num_uvar2), integer);
let num = num_num(attr_int); let num = num_num(attr_int);
let attr_num = attr_type(Bool::variable(num_uvar3), num); let attr_num = attr_type(Bool::variable(num_uvar3), num);
@ -438,10 +438,9 @@ fn unique_float(var_store: &mut VarStore) -> (Variable, Variable, Variable, Type
let num_uvar1 = var_store.fresh(); let num_uvar1 = var_store.fresh();
let num_uvar2 = var_store.fresh(); let num_uvar2 = var_store.fresh();
let num_uvar3 = var_store.fresh(); let num_uvar3 = var_store.fresh();
let num_uvar4 = var_store.fresh();
let binary_64 = num_binary64(); let fp = num_floatingpoint(Type::Variable(num_uvar4));
let attr_binary_64 = attr_type(Bool::variable(num_uvar1), binary_64);
let fp = num_floatingpoint(attr_binary_64);
let attr_fp = attr_type(Bool::variable(num_uvar2), fp); let attr_fp = attr_type(Bool::variable(num_uvar2), fp);
let num = num_num(attr_fp); let num = num_num(attr_fp);
let attr_num = attr_type(Bool::variable(num_uvar3), num); let attr_num = attr_type(Bool::variable(num_uvar3), num);
@ -478,7 +477,7 @@ pub fn constrain_expr(
]), ]),
) )
} }
Int(var, _) => { Int(var, _, _) => {
let (a, b, c, num_type) = unique_int(var_store); let (a, b, c, num_type) = unique_int(var_store);
exists( exists(
@ -494,7 +493,7 @@ pub fn constrain_expr(
]), ]),
) )
} }
Float(var, _) => { Float(var, _, _) => {
let (a, b, c, num_type) = unique_float(var_store); let (a, b, c, num_type) = unique_float(var_store);
exists( exists(
@ -532,7 +531,9 @@ pub fn constrain_expr(
), ),
) )
} }
Record { record_var, fields } => { Record {
record_var, fields, ..
} => {
// NOTE: canonicalization guarantees at least one field // NOTE: canonicalization guarantees at least one field
// zero fields generates an EmptyRecord // zero fields generates an EmptyRecord
let mut field_types = SendMap::default(); let mut field_types = SendMap::default();

View file

@ -762,8 +762,8 @@ mod test_fmt {
expr_formats_to( expr_formats_to(
indoc!( indoc!(
r#" r#"
f: { y : I64, f: { y : Int *,
x : I64 , x : Int * ,
} }
f"# f"#
@ -772,8 +772,8 @@ mod test_fmt {
r#" r#"
f : f :
{ {
y : I64, y : Int *,
x : I64, x : Int *,
} }
f"# f"#
@ -787,8 +787,8 @@ mod test_fmt {
r#" r#"
f : f :
{ {
y : I64, y : Int *,
x : I64, x : Int *,
} }
f"# f"#
@ -800,7 +800,7 @@ mod test_fmt {
expr_formats_same(indoc!( expr_formats_same(indoc!(
r#" r#"
f : f :
I64 Int *
f"# f"#
)); ));
@ -880,7 +880,7 @@ mod test_fmt {
r#" r#"
f : f :
{ {
x: I64 # comment 1 x: Int * # comment 1
, ,
# comment 2 # comment 2
} }
@ -891,7 +891,7 @@ mod test_fmt {
r#" r#"
f : f :
{ {
x : I64, x : Int *,
# comment 1 # comment 1
# comment 2 # comment 2
} }
@ -2557,7 +2557,7 @@ mod test_fmt {
fn record_type() { fn record_type() {
expr_formats_same(indoc!( expr_formats_same(indoc!(
r#" r#"
f : { foo : I64 } f : { foo : Int * }
f = { foo: 1000 } f = { foo: 1000 }
a a
@ -2608,11 +2608,11 @@ mod test_fmt {
// r#" // r#"
// f : // f :
// Result a // Result a
// { x : I64 // { x : Int *
// , y : Float // , y : Float
// } // }
// c // c
// -> I64 // -> Int *
// f = // f =
// \_ -> 4 // \_ -> 4
// "# // "#

View file

@ -19,6 +19,7 @@ use crate::llvm::refcounting::{
}; };
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use either::Either;
use inkwell::basic_block::BasicBlock; use inkwell::basic_block::BasicBlock;
use inkwell::builder::Builder; use inkwell::builder::Builder;
use inkwell::context::Context; use inkwell::context::Context;
@ -41,7 +42,7 @@ use roc_collections::all::{ImMap, MutSet};
use roc_module::ident::TagName; use roc_module::ident::TagName;
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::{JoinPointId, Wrapped}; use roc_mono::ir::{CallType, JoinPointId, Wrapped};
use roc_mono::layout::{Builtin, ClosureLayout, Layout, LayoutIds, MemoryMode}; use roc_mono::layout::{Builtin, ClosureLayout, Layout, LayoutIds, MemoryMode};
use target_lexicon::CallingConvention; use target_lexicon::CallingConvention;
@ -444,6 +445,14 @@ pub fn construct_optimization_passes<'a>(
fpm.add_memcpy_optimize_pass(); // this one is very important fpm.add_memcpy_optimize_pass(); // this one is very important
fpm.add_licm_pass(); fpm.add_licm_pass();
// turn invoke into call
mpm.add_prune_eh_pass();
// remove unused global values (often the `_wrapper` can be removed)
mpm.add_global_dce_pass();
mpm.add_function_inlining_pass();
} }
} }
@ -490,15 +499,52 @@ fn get_inplace_from_layout(layout: &Layout<'_>) -> InPlace {
} }
} }
pub fn int_with_precision<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
value: i128,
precision: &Builtin,
) -> IntValue<'ctx> {
match precision {
Builtin::Usize => ptr_int(env.context, env.ptr_bytes).const_int(value as u64, false),
Builtin::Int128 => const_i128(env, value),
Builtin::Int64 => env.context.i64_type().const_int(value as u64, false),
Builtin::Int32 => env.context.i32_type().const_int(value as u64, false),
Builtin::Int16 => env.context.i16_type().const_int(value as u64, false),
Builtin::Int8 => env.context.i8_type().const_int(value as u64, false),
_ => panic!("Invalid layout for int literal = {:?}", precision),
}
}
pub fn float_with_precision<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
value: f64,
precision: &Builtin,
) -> FloatValue<'ctx> {
match precision {
Builtin::Float64 => env.context.f64_type().const_float(value),
Builtin::Float32 => env.context.f32_type().const_float(value),
_ => panic!("Invalid layout for float literal = {:?}", precision),
}
}
pub fn build_exp_literal<'a, 'ctx, 'env>( pub fn build_exp_literal<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout: &Layout<'_>,
literal: &roc_mono::ir::Literal<'a>, literal: &roc_mono::ir::Literal<'a>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
use roc_mono::ir::Literal::*; use roc_mono::ir::Literal::*;
match literal { match literal {
Int(num) => env.context.i64_type().const_int(*num as u64, true).into(), Int(int) => match layout {
Float(num) => env.context.f64_type().const_float(*num).into(), Layout::Builtin(builtin) => int_with_precision(env, *int as i128, builtin).into(),
_ => panic!("Invalid layout for int literal = {:?}", layout),
},
Float(float) => match layout {
Layout::Builtin(builtin) => float_with_precision(env, *float, builtin).into(),
_ => panic!("Invalid layout for float literal = {:?}", layout),
},
Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(), Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(),
Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(), Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(),
Str(str_literal) => { Str(str_literal) => {
@ -625,101 +671,27 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
} }
} }
pub fn build_exp_expr<'a, 'ctx, 'env>( pub fn build_exp_call<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
scope: &Scope<'a, 'ctx>, scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
layout: &Layout<'a>, layout: &Layout<'a>,
expr: &roc_mono::ir::Expr<'a>, call: &roc_mono::ir::Call<'a>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
use roc_mono::ir::CallType::*; let roc_mono::ir::Call {
use roc_mono::ir::Expr::*; call_type,
match expr {
Literal(literal) => build_exp_literal(env, literal),
RunLowLevel(op, symbols) => {
run_low_level(env, layout_ids, scope, parent, layout, *op, symbols)
}
ForeignCall {
foreign_symbol,
arguments, arguments,
ret_layout, } = call;
match call_type {
CallType::ByName {
name, full_layout, ..
} => { } => {
let mut arg_vals: Vec<BasicValueEnum> = let mut arg_tuples: Vec<BasicValueEnum> =
Vec::with_capacity_in(arguments.len(), env.arena); Vec::with_capacity_in(arguments.len(), env.arena);
let mut arg_types = Vec::with_capacity_in(arguments.len() + 1, env.arena); for symbol in arguments.iter() {
// crude approximation of the C calling convention
let pass_result_by_pointer = ret_layout.stack_size(env.ptr_bytes) > 2 * env.ptr_bytes;
if pass_result_by_pointer {
// the return value is too big to pass through a register, so the caller must
// allocate space for it on its stack, and provide a pointer to write the result into
let ret_type =
basic_type_from_layout(env.arena, env.context, ret_layout, env.ptr_bytes);
let ret_ptr_type = get_ptr_type(&ret_type, AddressSpace::Generic);
let ret_ptr = env.builder.build_alloca(ret_type, "return_value");
arg_vals.push(ret_ptr.into());
arg_types.push(ret_ptr_type.into());
for arg in arguments.iter() {
let (value, layout) = load_symbol_and_layout(env, scope, arg);
arg_vals.push(value);
let arg_type =
basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes);
arg_types.push(arg_type);
}
let function_type = env.context.void_type().fn_type(&arg_types, false);
let function = get_foreign_symbol(env, foreign_symbol.clone(), function_type);
let call = env.builder.build_call(function, arg_vals.as_slice(), "tmp");
// this is a foreign function, use c calling convention
call.set_call_convention(C_CALL_CONV);
call.try_as_basic_value();
env.builder.build_load(ret_ptr, "read_result")
} else {
for arg in arguments.iter() {
let (value, layout) = load_symbol_and_layout(env, scope, arg);
arg_vals.push(value);
let arg_type =
basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes);
arg_types.push(arg_type);
}
let ret_type =
basic_type_from_layout(env.arena, env.context, ret_layout, env.ptr_bytes);
let function_type = get_fn_type(&ret_type, &arg_types);
let function = get_foreign_symbol(env, foreign_symbol.clone(), function_type);
let call = env.builder.build_call(function, arg_vals.as_slice(), "tmp");
// this is a foreign function, use c calling convention
call.set_call_convention(C_CALL_CONV);
call.try_as_basic_value()
.left()
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."))
}
}
FunctionCall {
call_type: ByName(name),
full_layout,
args,
..
} => {
let mut arg_tuples: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), env.arena);
for symbol in args.iter() {
arg_tuples.push(load_symbol(env, scope, symbol)); arg_tuples.push(load_symbol(env, scope, symbol));
} }
@ -733,16 +705,13 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
) )
} }
FunctionCall { CallType::ByPointer { name, .. } => {
call_type: ByPointer(name),
args,
..
} => {
let sub_expr = load_symbol(env, scope, name); let sub_expr = load_symbol(env, scope, name);
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), env.arena); let mut arg_vals: Vec<BasicValueEnum> =
Vec::with_capacity_in(arguments.len(), env.arena);
for arg in args.iter() { for arg in arguments.iter() {
arg_vals.push(load_symbol(env, scope, arg)); arg_vals.push(load_symbol(env, scope, arg));
} }
@ -771,6 +740,33 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer.")) .unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."))
} }
CallType::LowLevel { op } => {
run_low_level(env, layout_ids, scope, parent, layout, *op, arguments)
}
CallType::Foreign {
foreign_symbol: foreign,
ret_layout,
} => build_foreign_symbol(env, scope, foreign, arguments, ret_layout),
}
}
pub fn build_exp_expr<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
layout: &Layout<'a>,
expr: &roc_mono::ir::Expr<'a>,
) -> BasicValueEnum<'ctx> {
use inkwell::types::BasicType;
use roc_mono::ir::Expr::*;
match expr {
Literal(literal) => build_exp_literal(env, layout, literal),
Call(call) => build_exp_call(env, layout_ids, scope, parent, layout, call),
Struct(sorted_fields) => { Struct(sorted_fields) => {
let ctx = env.context; let ctx = env.context;
let builder = env.builder; let builder = env.builder;
@ -916,15 +912,9 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
field_types.push(field_type); field_types.push(field_type);
if let Layout::RecursivePointer = tag_field_layout { if let Layout::RecursivePointer = tag_field_layout {
let ptr = allocate_with_refcount(env, &tag_layout, val); panic!(
r"non-recursive tag unions cannot directly contain a recursive pointer"
let ptr = cast_basic_basic(
builder,
ptr.into(),
ctx.i64_type().ptr_type(AddressSpace::Generic).into(),
); );
field_vals.push(ptr);
} else { } else {
// this check fails for recursive tag unions, but can be helpful while debugging // this check fails for recursive tag unions, but can be helpful while debugging
debug_assert_eq!(tag_field_layout, val_layout); debug_assert_eq!(tag_field_layout, val_layout);
@ -983,7 +973,88 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
internal_type, internal_type,
) )
} }
Tag { .. } => unreachable!("tags should have a union layout"), Tag {
arguments,
tag_layout: Layout::RecursiveUnion(fields),
union_size,
tag_id,
tag_name,
..
} => {
let tag_layout = Layout::Union(fields);
debug_assert!(*union_size > 1);
let ptr_size = env.ptr_bytes;
let ctx = env.context;
let builder = env.builder;
// Determine types
let num_fields = arguments.len() + 1;
let mut field_types = Vec::with_capacity_in(num_fields, env.arena);
let mut field_vals = Vec::with_capacity_in(num_fields, env.arena);
let tag_field_layouts = if let TagName::Closure(_) = tag_name {
// closures ignore (and do not store) the discriminant
&fields[*tag_id as usize][1..]
} else {
&fields[*tag_id as usize]
};
for (field_symbol, tag_field_layout) in arguments.iter().zip(tag_field_layouts.iter()) {
let (val, val_layout) = load_symbol_and_layout(env, scope, field_symbol);
// Zero-sized fields have no runtime representation.
// The layout of the struct expects them to be dropped!
if !tag_field_layout.is_dropped_because_empty() {
let field_type =
basic_type_from_layout(env.arena, env.context, tag_field_layout, ptr_size);
field_types.push(field_type);
if let Layout::RecursivePointer = tag_field_layout {
debug_assert!(val.is_pointer_value());
// we store recursive pointers as `i64*`
let ptr = cast_basic_basic(
builder,
val,
ctx.i64_type().ptr_type(AddressSpace::Generic).into(),
);
field_vals.push(ptr);
} else {
// this check fails for recursive tag unions, but can be helpful while debugging
debug_assert_eq!(tag_field_layout, val_layout);
field_vals.push(val);
}
}
}
// Create the struct_type
let data_ptr = reserve_with_refcount(env, &tag_layout);
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
let struct_ptr = cast_basic_basic(
builder,
data_ptr.into(),
struct_type.ptr_type(AddressSpace::Generic).into(),
)
.into_pointer_value();
// Insert field exprs into struct_val
for (index, field_val) in field_vals.into_iter().enumerate() {
let field_ptr = builder
.build_struct_gep(struct_ptr, index as u32, "struct_gep")
.unwrap();
builder.build_store(field_ptr, field_val);
}
data_ptr.into()
}
Tag { .. } => unreachable!("tags should have a Union or RecursiveUnion layout"),
Reset(_) => todo!(), Reset(_) => todo!(),
Reuse { .. } => todo!(), Reuse { .. } => todo!(),
@ -1048,6 +1119,8 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
field_layouts, field_layouts,
.. ..
} => { } => {
use BasicValueEnum::*;
let builder = env.builder; let builder = env.builder;
// Determine types, assumes the descriminant is in the field layouts // Determine types, assumes the descriminant is in the field layouts
@ -1067,20 +1140,22 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
.struct_type(field_types.into_bump_slice(), false); .struct_type(field_types.into_bump_slice(), false);
// cast the argument bytes into the desired shape for this tag // cast the argument bytes into the desired shape for this tag
let argument = load_symbol(env, scope, structure).into_struct_value(); let argument = load_symbol(env, scope, structure);
let struct_value = cast_struct_struct(builder, argument, struct_type); let struct_layout = Layout::Struct(field_layouts);
match argument {
StructValue(value) => {
let struct_value = cast_struct_struct(builder, value, struct_type);
let result = builder let result = builder
.build_extract_value(struct_value, *index as u32, "") .build_extract_value(struct_value, *index as u32, "")
.expect("desired field did not decode"); .expect("desired field did not decode");
if let Some(Layout::RecursivePointer) = field_layouts.get(*index as usize) { if let Some(Layout::RecursivePointer) = field_layouts.get(*index as usize) {
let struct_layout = Layout::Struct(field_layouts); let desired_type =
let desired_type = block_of_memory(env.context, &struct_layout, env.ptr_bytes); block_of_memory(env.context, &struct_layout, env.ptr_bytes);
// the value is a pointer to the actual value; load that value! // the value is a pointer to the actual value; load that value!
use inkwell::types::BasicType;
let ptr = cast_basic_basic( let ptr = cast_basic_basic(
builder, builder,
result, result,
@ -1091,6 +1166,37 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
result result
} }
} }
PointerValue(value) => {
let ptr = cast_basic_basic(
builder,
value.into(),
struct_type.ptr_type(AddressSpace::Generic).into(),
)
.into_pointer_value();
let elem_ptr = builder
.build_struct_gep(ptr, *index as u32, "at_index_struct_gep")
.unwrap();
let result = builder.build_load(elem_ptr, "load_at_index_ptr");
if let Some(Layout::RecursivePointer) = field_layouts.get(*index as usize) {
// a recursive field is stored as a `i64*`, to use it we must cast it to
// a pointer to the block of memory representation
cast_basic_basic(
builder,
result,
block_of_memory(env.context, &struct_layout, env.ptr_bytes)
.ptr_type(AddressSpace::Generic)
.into(),
)
} else {
result
}
}
_ => panic!("cannot look up index in {:?}", argument),
}
}
EmptyArray => empty_polymorphic_list(env), EmptyArray => empty_polymorphic_list(env),
Array { elem_layout, elems } => { Array { elem_layout, elems } => {
let inplace = get_inplace_from_layout(layout); let inplace = get_inplace_from_layout(layout);
@ -1134,12 +1240,10 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
} }
} }
pub fn allocate_with_refcount<'a, 'ctx, 'env>( pub fn reserve_with_refcount<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout: &Layout<'a>, layout: &Layout<'a>,
value: BasicValueEnum<'ctx>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
let builder = env.builder;
let ctx = env.context; let ctx = env.context;
let len_type = env.ptr_int(); let len_type = env.ptr_int();
@ -1149,10 +1253,18 @@ pub fn allocate_with_refcount<'a, 'ctx, 'env>(
let rc1 = crate::llvm::refcounting::refcount_1(ctx, env.ptr_bytes); let rc1 = crate::llvm::refcounting::refcount_1(ctx, env.ptr_bytes);
let data_ptr = allocate_with_refcount_help(env, layout, value_bytes_intvalue, rc1); allocate_with_refcount_help(env, layout, value_bytes_intvalue, rc1)
}
pub fn allocate_with_refcount<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout: &Layout<'a>,
value: BasicValueEnum<'ctx>,
) -> PointerValue<'ctx> {
let data_ptr = reserve_with_refcount(env, layout);
// store the value in the pointer // store the value in the pointer
builder.build_store(data_ptr, value); env.builder.build_store(data_ptr, value);
data_ptr data_ptr
} }
@ -1299,6 +1411,92 @@ fn list_literal<'a, 'ctx, 'env>(
) )
} }
#[allow(clippy::too_many_arguments)]
fn invoke_roc_function<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
scope: &mut Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
symbol: Symbol,
layout: Layout<'a>,
function_value: Either<FunctionValue<'ctx>, PointerValue<'ctx>>,
arguments: &[Symbol],
pass: &'a roc_mono::ir::Stmt<'a>,
fail: &'a roc_mono::ir::Stmt<'a>,
) -> BasicValueEnum<'ctx> {
let context = env.context;
let call_bt = basic_type_from_layout(env.arena, context, &layout, env.ptr_bytes);
let alloca = create_entry_block_alloca(env, parent, call_bt, symbol.ident_string(&env.interns));
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(arguments.len(), env.arena);
for arg in arguments.iter() {
arg_vals.push(load_symbol(env, scope, arg));
}
let pass_block = context.append_basic_block(parent, "invoke_pass");
let fail_block = context.append_basic_block(parent, "invoke_fail");
let call_result = {
let call = env.builder.build_invoke(
function_value,
arg_vals.as_slice(),
pass_block,
fail_block,
"tmp",
);
match function_value {
Either::Left(function) => {
call.set_call_convention(function.get_call_conventions());
}
Either::Right(_) => {
call.set_call_convention(FAST_CALL_CONV);
}
}
call.try_as_basic_value()
.left()
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."))
};
{
env.builder.position_at_end(pass_block);
env.builder.build_store(alloca, call_result);
scope.insert(symbol, (layout, alloca));
build_exp_stmt(env, layout_ids, scope, parent, pass);
scope.remove(&symbol);
}
{
env.builder.position_at_end(fail_block);
let landing_pad_type = {
let exception_ptr = context.i8_type().ptr_type(AddressSpace::Generic).into();
let selector_value = context.i32_type().into();
context.struct_type(&[exception_ptr, selector_value], false)
};
env.builder
.build_catch_all_landing_pad(
&landing_pad_type,
&BasicValueEnum::IntValue(context.i8_type().const_zero()),
context.i8_type().ptr_type(AddressSpace::Generic),
"invoke_landing_pad",
)
.into_struct_value();
build_exp_stmt(env, layout_ids, scope, parent, fail);
}
call_result
}
pub fn build_exp_stmt<'a, 'ctx, 'env>( pub fn build_exp_stmt<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
@ -1324,6 +1522,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
let mut stack = Vec::with_capacity_in(queue.len(), env.arena); let mut stack = Vec::with_capacity_in(queue.len(), env.arena);
for (symbol, expr, layout) in queue { for (symbol, expr, layout) in queue {
debug_assert!(layout != &Layout::RecursivePointer);
let context = &env.context; let context = &env.context;
let val = build_exp_expr(env, layout_ids, &scope, parent, layout, &expr); let val = build_exp_expr(env, layout_ids, &scope, parent, layout, &expr);
@ -1381,6 +1580,92 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
value value
} }
Invoke {
symbol,
call,
layout,
pass,
fail: roc_mono::ir::Stmt::Rethrow,
} => {
// when the fail case is just Rethrow, there is no cleanup work to do
// so we can just treat this invoke as a normal call
let stmt =
roc_mono::ir::Stmt::Let(*symbol, Expr::Call(call.clone()), layout.clone(), pass);
build_exp_stmt(env, layout_ids, scope, parent, &stmt)
}
Invoke {
symbol,
call,
layout,
pass,
fail,
} => match call.call_type {
CallType::ByName {
name,
ref full_layout,
..
} => {
let function_value = function_value_by_name(env, layout_ids, full_layout, name);
invoke_roc_function(
env,
layout_ids,
scope,
parent,
*symbol,
layout.clone(),
function_value.into(),
call.arguments,
pass,
fail,
)
}
CallType::ByPointer { name, .. } => {
let sub_expr = load_symbol(env, scope, &name);
let function_ptr = match sub_expr {
BasicValueEnum::PointerValue(ptr) => ptr,
non_ptr => {
panic!(
"Tried to call by pointer, but encountered a non-pointer: {:?}",
non_ptr
);
}
};
invoke_roc_function(
env,
layout_ids,
scope,
parent,
*symbol,
layout.clone(),
function_ptr.into(),
call.arguments,
pass,
fail,
)
}
CallType::Foreign {
ref foreign_symbol,
ref ret_layout,
} => build_foreign_symbol(env, scope, foreign_symbol, call.arguments, ret_layout),
CallType::LowLevel { .. } => {
unreachable!("lowlevel itself never throws exceptions")
}
},
Rethrow => {
cxa_rethrow_exception(env);
// used in exception handling
env.builder.build_unreachable();
env.context.i64_type().const_zero().into()
}
Switch { Switch {
branches, branches,
default_branch, default_branch,
@ -1594,6 +1879,22 @@ struct SwitchArgsIr<'a, 'ctx> {
pub ret_type: BasicTypeEnum<'ctx>, pub ret_type: BasicTypeEnum<'ctx>,
} }
fn const_i128<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, value: i128) -> IntValue<'ctx> {
// TODO verify the order [a, b] is correct for larger numbers when we can parse them
debug_assert!(value <= i64::MAX as i128);
// truncate the lower 64 bits
let value = value as u128;
let a = value as u64;
// get the upper 64 bits
let b = (value >> 64) as u64;
env.context
.i128_type()
.const_int_arbitrary_precision(&[a, b])
}
fn build_switch_ir<'a, 'ctx, 'env>( fn build_switch_ir<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
@ -1631,6 +1932,15 @@ fn build_switch_ir<'a, 'ctx, 'env>(
.build_bitcast(full_cond, env.context.i64_type(), "") .build_bitcast(full_cond, env.context.i64_type(), "")
.into_int_value() .into_int_value()
} }
Layout::Builtin(Builtin::Float32) => {
// float matches are done on the bit pattern
cond_layout = Layout::Builtin(Builtin::Int32);
let full_cond = load_symbol(env, scope, cond_symbol);
builder
.build_bitcast(full_cond, env.context.i32_type(), "")
.into_int_value()
}
Layout::Union(_) => { Layout::Union(_) => {
// we match on the discriminant, not the whole Tag // we match on the discriminant, not the whole Tag
cond_layout = Layout::Builtin(Builtin::Int64); cond_layout = Layout::Builtin(Builtin::Int64);
@ -1658,8 +1968,11 @@ fn build_switch_ir<'a, 'ctx, 'env>(
// //
// they either need to all be i8, or i64 // they either need to all be i8, or i64
let int_val = match cond_layout { let int_val = match cond_layout {
Layout::Builtin(Builtin::Int128) => context.i128_type().const_int(*int as u64, false), /* TODO file an issue: you can't currently have an int literal bigger than 64 bits long, and also (as we see here), you can't currently have (at least in Inkwell) a when-branch with an i128 literal in its pattren */ Layout::Builtin(Builtin::Usize) => {
ptr_int(env.context, env.ptr_bytes).const_int(*int as u64, false)
}
Layout::Builtin(Builtin::Int64) => context.i64_type().const_int(*int as u64, false), Layout::Builtin(Builtin::Int64) => context.i64_type().const_int(*int as u64, false),
Layout::Builtin(Builtin::Int128) => const_i128(env, *int as i128),
Layout::Builtin(Builtin::Int32) => context.i32_type().const_int(*int as u64, false), Layout::Builtin(Builtin::Int32) => context.i32_type().const_int(*int as u64, false),
Layout::Builtin(Builtin::Int16) => context.i16_type().const_int(*int as u64, false), Layout::Builtin(Builtin::Int16) => context.i16_type().const_int(*int as u64, false),
Layout::Builtin(Builtin::Int8) => context.i8_type().const_int(*int as u64, false), Layout::Builtin(Builtin::Int8) => context.i8_type().const_int(*int as u64, false),
@ -2027,7 +2340,11 @@ fn make_exception_catcher<'a, 'ctx, 'env>(
) -> FunctionValue<'ctx> { ) -> FunctionValue<'ctx> {
let wrapper_function_name = format!("{}_catcher", roc_function.get_name().to_str().unwrap()); let wrapper_function_name = format!("{}_catcher", roc_function.get_name().to_str().unwrap());
make_exception_catching_wrapper(env, roc_function, &wrapper_function_name) let function_value = make_exception_catching_wrapper(env, roc_function, &wrapper_function_name);
function_value.set_linkage(Linkage::Internal);
function_value
} }
fn make_exception_catching_wrapper<'a, 'ctx, 'env>( fn make_exception_catching_wrapper<'a, 'ctx, 'env>(
@ -2539,6 +2856,29 @@ pub fn verify_fn(fn_val: FunctionValue<'_>) {
} }
} }
fn function_value_by_name<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
symbol: Symbol,
) -> FunctionValue<'ctx> {
let fn_name = layout_ids
.get(symbol, layout)
.to_symbol_string(symbol, &env.interns);
let fn_name = fn_name.as_str();
env.module.get_function(fn_name).unwrap_or_else(|| {
if symbol.is_builtin() {
panic!("Unrecognized builtin function: {:?}", fn_name)
} else {
panic!(
"Unrecognized non-builtin function: {:?} (symbol: {:?}, layout: {:?})",
fn_name, symbol, layout
)
}
})
}
// #[allow(clippy::cognitive_complexity)] // #[allow(clippy::cognitive_complexity)]
#[inline(always)] #[inline(always)]
fn call_with_args<'a, 'ctx, 'env>( fn call_with_args<'a, 'ctx, 'env>(
@ -2549,21 +2889,7 @@ fn call_with_args<'a, 'ctx, 'env>(
_parent: FunctionValue<'ctx>, _parent: FunctionValue<'ctx>,
args: &[BasicValueEnum<'ctx>], args: &[BasicValueEnum<'ctx>],
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let fn_name = layout_ids let fn_val = function_value_by_name(env, layout_ids, layout, symbol);
.get(symbol, layout)
.to_symbol_string(symbol, &env.interns);
let fn_name = fn_name.as_str();
let fn_val = env.module.get_function(fn_name).unwrap_or_else(|| {
if symbol.is_builtin() {
panic!("Unrecognized builtin function: {:?}", fn_name)
} else {
panic!(
"Unrecognized non-builtin function: {:?} (symbol: {:?}, layout: {:?})",
fn_name, symbol, layout
)
}
});
let call = env.builder.build_call(fn_val, args, "call"); let call = env.builder.build_call(fn_val, args, "call");
@ -2746,7 +3072,16 @@ fn run_low_level<'a, 'ctx, 'env>(
let inplace = get_inplace_from_layout(layout); let inplace = get_inplace_from_layout(layout);
list_keep_if(env, inplace, parent, func, func_layout, list, list_layout) list_keep_if(
env,
layout_ids,
inplace,
parent,
func,
func_layout,
list,
list_layout,
)
} }
ListContains => { ListContains => {
// List.contains : List elem, elem -> Bool // List.contains : List elem, elem -> Bool
@ -2756,7 +3091,15 @@ fn run_low_level<'a, 'ctx, 'env>(
let (elem, elem_layout) = load_symbol_and_layout(env, scope, &args[1]); let (elem, elem_layout) = load_symbol_and_layout(env, scope, &args[1]);
list_contains(env, parent, elem, elem_layout, list, list_layout) list_contains(
env,
layout_ids,
parent,
elem,
elem_layout,
list,
list_layout,
)
} }
ListWalk => { ListWalk => {
debug_assert_eq!(args.len(), 3); debug_assert_eq!(args.len(), 3);
@ -2849,7 +3192,7 @@ fn run_low_level<'a, 'ctx, 'env>(
use roc_mono::layout::Builtin::*; use roc_mono::layout::Builtin::*;
match arg_builtin { match arg_builtin {
Int128 | Int64 | Int32 | Int16 | Int8 => { Usize | Int128 | Int64 | Int32 | Int16 | Int8 => {
build_int_unary_op(env, arg.into_int_value(), arg_builtin, op) build_int_unary_op(env, arg.into_int_value(), arg_builtin, op)
} }
Float128 | Float64 | Float32 | Float16 => { Float128 | Float64 | Float32 | Float16 => {
@ -2887,7 +3230,7 @@ fn run_low_level<'a, 'ctx, 'env>(
let tag_lt = env.context.i8_type().const_int(2_u64, false); let tag_lt = env.context.i8_type().const_int(2_u64, false);
match lhs_builtin { match lhs_builtin {
Int128 | Int64 | Int32 | Int16 | Int8 => { Usize | Int128 | Int64 | Int32 | Int16 | Int8 => {
let are_equal = env.builder.build_int_compare( let are_equal = env.builder.build_int_compare(
IntPredicate::EQ, IntPredicate::EQ,
lhs_arg.into_int_value(), lhs_arg.into_int_value(),
@ -2959,7 +3302,7 @@ fn run_low_level<'a, 'ctx, 'env>(
build_num_binop(env, parent, lhs_arg, lhs_layout, rhs_arg, rhs_layout, op) build_num_binop(env, parent, lhs_arg, lhs_layout, rhs_arg, rhs_layout, op)
} }
NumBitwiseAnd => { NumBitwiseAnd | NumBitwiseXor => {
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);
let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]); let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]);
@ -2981,7 +3324,7 @@ fn run_low_level<'a, 'ctx, 'env>(
let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]); let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]);
let (rhs_arg, rhs_layout) = load_symbol_and_layout(env, scope, &args[1]); let (rhs_arg, rhs_layout) = load_symbol_and_layout(env, scope, &args[1]);
build_eq(env, lhs_arg, rhs_arg, lhs_layout, rhs_layout) build_eq(env, layout_ids, lhs_arg, rhs_arg, lhs_layout, rhs_layout)
} }
NotEq => { NotEq => {
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);
@ -2989,7 +3332,7 @@ fn run_low_level<'a, 'ctx, 'env>(
let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]); let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]);
let (rhs_arg, rhs_layout) = load_symbol_and_layout(env, scope, &args[1]); let (rhs_arg, rhs_layout) = load_symbol_and_layout(env, scope, &args[1]);
build_neq(env, lhs_arg, rhs_arg, lhs_layout, rhs_layout) build_neq(env, layout_ids, lhs_arg, rhs_arg, lhs_layout, rhs_layout)
} }
And => { And => {
// The (&&) operator // The (&&) operator
@ -3095,6 +3438,73 @@ fn run_low_level<'a, 'ctx, 'env>(
} }
} }
fn build_foreign_symbol<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
foreign: &roc_module::ident::ForeignSymbol,
arguments: &[Symbol],
ret_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(arguments.len(), env.arena);
let mut arg_types = Vec::with_capacity_in(arguments.len() + 1, env.arena);
// crude approximation of the C calling convention
let pass_result_by_pointer = ret_layout.stack_size(env.ptr_bytes) > 2 * env.ptr_bytes;
if pass_result_by_pointer {
// the return value is too big to pass through a register, so the caller must
// allocate space for it on its stack, and provide a pointer to write the result into
let ret_type = basic_type_from_layout(env.arena, env.context, ret_layout, env.ptr_bytes);
let ret_ptr_type = get_ptr_type(&ret_type, AddressSpace::Generic);
let ret_ptr = env.builder.build_alloca(ret_type, "return_value");
arg_vals.push(ret_ptr.into());
arg_types.push(ret_ptr_type.into());
for arg in arguments.iter() {
let (value, layout) = load_symbol_and_layout(env, scope, arg);
arg_vals.push(value);
let arg_type = basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes);
arg_types.push(arg_type);
}
let function_type = env.context.void_type().fn_type(&arg_types, false);
let function = get_foreign_symbol(env, foreign.clone(), function_type);
let call = env.builder.build_call(function, arg_vals.as_slice(), "tmp");
// this is a foreign function, use c calling convention
call.set_call_convention(C_CALL_CONV);
call.try_as_basic_value();
env.builder.build_load(ret_ptr, "read_result")
} else {
for arg in arguments.iter() {
let (value, layout) = load_symbol_and_layout(env, scope, arg);
arg_vals.push(value);
let arg_type = basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes);
arg_types.push(arg_type);
}
let ret_type = basic_type_from_layout(env.arena, env.context, ret_layout, env.ptr_bytes);
let function_type = get_fn_type(&ret_type, &arg_types);
let function = get_foreign_symbol(env, foreign.clone(), function_type);
let call = env.builder.build_call(function, arg_vals.as_slice(), "tmp");
// this is a foreign function, use c calling convention
call.set_call_convention(C_CALL_CONV);
call.try_as_basic_value()
.left()
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."))
}
}
fn maybe_inplace_list<'a, 'ctx, 'env, InPlace, CloneFirst, Empty>( fn maybe_inplace_list<'a, 'ctx, 'env, InPlace, CloneFirst, Empty>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
@ -3251,6 +3661,7 @@ fn build_int_binop<'a, 'ctx, 'env>(
NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_int").into(), NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_int").into(),
NumPowInt => call_bitcode_fn(env, &[lhs.into(), rhs.into()], &bitcode::NUM_POW_INT), NumPowInt => call_bitcode_fn(env, &[lhs.into(), rhs.into()], &bitcode::NUM_POW_INT),
NumBitwiseAnd => bd.build_and(lhs, rhs, "int_bitwise_and").into(), NumBitwiseAnd => bd.build_and(lhs, rhs, "int_bitwise_and").into(),
NumBitwiseXor => bd.build_xor(lhs, rhs, "int_bitwise_xor").into(),
_ => { _ => {
unreachable!("Unrecognized int binary operation: {:?}", op); unreachable!("Unrecognized int binary operation: {:?}", op);
} }
@ -3316,7 +3727,7 @@ pub fn build_num_binop<'a, 'ctx, 'env>(
use roc_mono::layout::Builtin::*; use roc_mono::layout::Builtin::*;
match lhs_builtin { match lhs_builtin {
Int128 | Int64 | Int32 | Int16 | Int8 => build_int_binop( Usize | Int128 | Int64 | Int32 | Int16 | Int8 => build_int_binop(
env, env,
parent, parent,
lhs_arg.into_int_value(), lhs_arg.into_int_value(),
@ -3567,6 +3978,7 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
int_abs_raise_on_overflow(env, arg, arg_layout) int_abs_raise_on_overflow(env, arg, arg_layout)
} }
NumToFloat => { NumToFloat => {
// TODO: Handle differnt sized numbers
// This is an Int, so we need to convert it. // This is an Int, so we need to convert it.
bd.build_cast( bd.build_cast(
InstructionOpcode::SIToFP, InstructionOpcode::SIToFP,
@ -3693,6 +4105,7 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
let bd = env.builder; let bd = env.builder;
// TODO: Handle differnt sized floats
match op { match op {
NumNeg => bd.build_float_neg(arg, "negate_float").into(), NumNeg => bd.build_float_neg(arg, "negate_float").into(),
NumAbs => env.call_intrinsic(LLVM_FABS_F64, &[arg.into()]), NumAbs => env.call_intrinsic(LLVM_FABS_F64, &[arg.into()]),
@ -3871,8 +4284,7 @@ fn cxa_throw_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, info: BasicVal
call.set_call_convention(C_CALL_CONV); call.set_call_convention(C_CALL_CONV);
} }
#[allow(dead_code)] fn cxa_rethrow_exception(env: &Env<'_, '_, '_>) {
fn cxa_rethrow_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> {
let name = "__cxa_rethrow"; let name = "__cxa_rethrow";
let module = env.module; let module = env.module;
@ -3891,10 +4303,10 @@ fn cxa_rethrow_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValu
cxa_rethrow cxa_rethrow
} }
}; };
let call = env.builder.build_call(function, &[], "never_used"); let call = env.builder.build_call(function, &[], "rethrow");
call.set_call_convention(C_CALL_CONV); call.set_call_convention(C_CALL_CONV);
call.try_as_basic_value().left().unwrap() // call.try_as_basic_value().left().unwrap()
} }
fn get_foreign_symbol<'a, 'ctx, 'env>( fn get_foreign_symbol<'a, 'ctx, 'env>(

View file

@ -1003,6 +1003,7 @@ pub fn list_walk_backwards<'a, 'ctx, 'env>(
/// List.contains : List elem, elem -> Bool /// List.contains : List elem, elem -> Bool
pub fn list_contains<'a, 'ctx, 'env>( pub fn list_contains<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
elem: BasicValueEnum<'ctx>, elem: BasicValueEnum<'ctx>,
elem_layout: &Layout<'a>, elem_layout: &Layout<'a>,
@ -1034,6 +1035,7 @@ pub fn list_contains<'a, 'ctx, 'env>(
list_contains_help( list_contains_help(
env, env,
layout_ids,
parent, parent,
length, length,
list_ptr, list_ptr,
@ -1043,8 +1045,10 @@ pub fn list_contains<'a, 'ctx, 'env>(
) )
} }
#[allow(clippy::too_many_arguments)]
pub fn list_contains_help<'a, 'ctx, 'env>( pub fn list_contains_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
length: IntValue<'ctx>, length: IntValue<'ctx>,
source_ptr: PointerValue<'ctx>, source_ptr: PointerValue<'ctx>,
@ -1082,7 +1086,14 @@ pub fn list_contains_help<'a, 'ctx, 'env>(
let current_elem = builder.build_load(current_elem_ptr, "load_elem"); let current_elem = builder.build_load(current_elem_ptr, "load_elem");
let has_found = build_eq(env, current_elem, elem, list_elem_layout, elem_layout); let has_found = build_eq(
env,
layout_ids,
current_elem,
elem,
list_elem_layout,
elem_layout,
);
builder.build_store(bool_alloca, has_found.into_int_value()); builder.build_store(bool_alloca, has_found.into_int_value());
@ -1111,8 +1122,10 @@ pub fn list_contains_help<'a, 'ctx, 'env>(
} }
/// List.keepIf : List elem, (elem -> Bool) -> List elem /// List.keepIf : List elem, (elem -> Bool) -> List elem
#[allow(clippy::too_many_arguments)]
pub fn list_keep_if<'a, 'ctx, 'env>( pub fn list_keep_if<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
output_inplace: InPlace, output_inplace: InPlace,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
func: BasicValueEnum<'ctx>, func: BasicValueEnum<'ctx>,
@ -1212,6 +1225,9 @@ pub fn list_keep_if<'a, 'ctx, 'env>(
builder.position_at_end(cont_block); builder.position_at_end(cont_block);
// consume the input list
decrement_refcount_layout(env, parent, layout_ids, list, list_layout);
builder.build_load(result, "load_result") builder.build_load(result, "load_result")
} }
} }

View file

@ -77,6 +77,28 @@ fn str_symbol_to_i128<'a, 'ctx, 'env>(
.into_int_value() .into_int_value()
} }
fn str_to_i128<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
value: BasicValueEnum<'ctx>,
) -> IntValue<'ctx> {
let cell = env.builder.build_alloca(value.get_type(), "cell");
env.builder.build_store(cell, value);
let i128_ptr = env
.builder
.build_bitcast(
cell,
env.context.i128_type().ptr_type(AddressSpace::Generic),
"cast",
)
.into_pointer_value();
env.builder
.build_load(i128_ptr, "load_as_i128")
.into_int_value()
}
fn zig_str_to_struct<'a, 'ctx, 'env>( fn zig_str_to_struct<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
zig_str: StructValue<'ctx>, zig_str: StructValue<'ctx>,
@ -222,3 +244,19 @@ pub fn str_from_int<'a, 'ctx, 'env>(
zig_str_to_struct(env, zig_result).into() zig_str_to_struct(env, zig_result).into()
} }
/// Str.equal : Str, Str -> Bool
pub fn str_equal<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
value1: BasicValueEnum<'ctx>,
value2: BasicValueEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
let str1_i128 = str_to_i128(env, value1);
let str2_i128 = str_to_i128(env, value2);
call_bitcode_fn(
env,
&[str1_i128.into(), str2_i128.into()],
&bitcode::STR_EQUAL,
)
}

View file

@ -1,10 +1,16 @@
use crate::llvm::build::Env; use crate::llvm::build::Env;
use inkwell::values::BasicValueEnum; use crate::llvm::build::{set_name, FAST_CALL_CONV};
use inkwell::{FloatPredicate, IntPredicate}; use crate::llvm::build_list::{list_len, load_list_ptr};
use roc_mono::layout::{Builtin, Layout}; use crate::llvm::build_str::str_equal;
use crate::llvm::convert::{basic_type_from_layout, get_ptr_type};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, StructValue};
use inkwell::{AddressSpace, FloatPredicate, IntPredicate};
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds};
pub fn build_eq<'a, 'ctx, 'env>( pub fn build_eq<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
lhs_val: BasicValueEnum<'ctx>, lhs_val: BasicValueEnum<'ctx>,
rhs_val: BasicValueEnum<'ctx>, rhs_val: BasicValueEnum<'ctx>,
lhs_layout: &Layout<'a>, lhs_layout: &Layout<'a>,
@ -43,6 +49,26 @@ pub fn build_eq<'a, 'ctx, 'env>(
(Builtin::Int1, Builtin::Int1) => int_cmp(IntPredicate::EQ, "eq_i1"), (Builtin::Int1, Builtin::Int1) => int_cmp(IntPredicate::EQ, "eq_i1"),
(Builtin::Float64, Builtin::Float64) => float_cmp(FloatPredicate::OEQ, "eq_f64"), (Builtin::Float64, Builtin::Float64) => float_cmp(FloatPredicate::OEQ, "eq_f64"),
(Builtin::Float32, Builtin::Float32) => float_cmp(FloatPredicate::OEQ, "eq_f32"), (Builtin::Float32, Builtin::Float32) => float_cmp(FloatPredicate::OEQ, "eq_f32"),
(Builtin::Str, Builtin::Str) => str_equal(env, lhs_val, rhs_val),
(Builtin::EmptyList, Builtin::EmptyList) => {
env.context.bool_type().const_int(1, false).into()
}
(Builtin::List(_, _), Builtin::EmptyList)
| (Builtin::EmptyList, Builtin::List(_, _)) => {
unreachable!("the `==` operator makes sure its two arguments have the same type and thus layout")
}
(Builtin::List(_, elem1), Builtin::List(_, elem2)) => {
debug_assert_eq!(elem1, elem2);
build_list_eq(
env,
layout_ids,
lhs_layout,
elem1,
lhs_val.into_struct_value(),
rhs_val.into_struct_value(),
)
}
(b1, b2) => { (b1, b2) => {
todo!("Handle equals for builtin layouts {:?} == {:?}", b1, b2); todo!("Handle equals for builtin layouts {:?} == {:?}", b1, b2);
} }
@ -57,6 +83,7 @@ pub fn build_eq<'a, 'ctx, 'env>(
pub fn build_neq<'a, 'ctx, 'env>( pub fn build_neq<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
lhs_val: BasicValueEnum<'ctx>, lhs_val: BasicValueEnum<'ctx>,
rhs_val: BasicValueEnum<'ctx>, rhs_val: BasicValueEnum<'ctx>,
lhs_layout: &Layout<'a>, lhs_layout: &Layout<'a>,
@ -95,6 +122,35 @@ pub fn build_neq<'a, 'ctx, 'env>(
(Builtin::Int1, Builtin::Int1) => int_cmp(IntPredicate::NE, "neq_i1"), (Builtin::Int1, Builtin::Int1) => int_cmp(IntPredicate::NE, "neq_i1"),
(Builtin::Float64, Builtin::Float64) => float_cmp(FloatPredicate::ONE, "neq_f64"), (Builtin::Float64, Builtin::Float64) => float_cmp(FloatPredicate::ONE, "neq_f64"),
(Builtin::Float32, Builtin::Float32) => float_cmp(FloatPredicate::ONE, "neq_f32"), (Builtin::Float32, Builtin::Float32) => float_cmp(FloatPredicate::ONE, "neq_f32"),
(Builtin::Str, Builtin::Str) => {
let is_equal = str_equal(env, lhs_val, rhs_val).into_int_value();
let result: IntValue = env.builder.build_not(is_equal, "negate");
result.into()
}
(Builtin::EmptyList, Builtin::EmptyList) => {
env.context.bool_type().const_int(0, false).into()
}
(Builtin::List(_, _), Builtin::EmptyList)
| (Builtin::EmptyList, Builtin::List(_, _)) => {
unreachable!("the `==` operator makes sure its two arguments have the same type and thus layout")
}
(Builtin::List(_, elem1), Builtin::List(_, elem2)) => {
debug_assert_eq!(elem1, elem2);
let equal = build_list_eq(
env,
layout_ids,
lhs_layout,
elem1,
lhs_val.into_struct_value(),
rhs_val.into_struct_value(),
);
let not_equal: IntValue = env.builder.build_not(equal.into_int_value(), "not");
not_equal.into()
}
(b1, b2) => { (b1, b2) => {
todo!("Handle not equals for builtin layouts {:?} == {:?}", b1, b2); todo!("Handle not equals for builtin layouts {:?} == {:?}", b1, b2);
} }
@ -110,3 +166,203 @@ pub fn build_neq<'a, 'ctx, 'env>(
} }
} }
} }
fn build_list_eq<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
list_layout: &Layout<'a>,
element_layout: &Layout<'a>,
list1: StructValue<'ctx>,
list2: StructValue<'ctx>,
) -> BasicValueEnum<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let symbol = Symbol::LIST_EQ;
let fn_name = layout_ids
.get(symbol, &element_layout)
.to_symbol_string(symbol, &env.interns);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let arena = env.arena;
let arg_type = basic_type_from_layout(arena, env.context, &list_layout, env.ptr_bytes);
let function_value = crate::llvm::refcounting::build_header_help(
env,
&fn_name,
env.context.bool_type().into(),
&[arg_type, arg_type],
);
build_list_eq_help(env, layout_ids, function_value, element_layout);
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
let call = env
.builder
.build_call(function, &[list1.into(), list2.into()], "list_eq");
call.set_call_convention(FAST_CALL_CONV);
call.try_as_basic_value().left().unwrap()
}
fn build_list_eq_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
parent: FunctionValue<'ctx>,
element_layout: &Layout<'a>,
) {
let ctx = env.context;
let builder = env.builder;
{
use inkwell::debug_info::AsDIScope;
let func_scope = parent.get_subprogram().unwrap();
let lexical_block = env.dibuilder.create_lexical_block(
/* scope */ func_scope.as_debug_info_scope(),
/* file */ env.compile_unit.get_file(),
/* line_no */ 0,
/* column_no */ 0,
);
let loc = env.dibuilder.create_debug_location(
ctx,
/* line */ 0,
/* column */ 0,
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
builder.set_current_debug_location(&ctx, loc);
}
// Add args to scope
let mut it = parent.get_param_iter();
let list1 = it.next().unwrap().into_struct_value();
let list2 = it.next().unwrap().into_struct_value();
set_name(list1.into(), Symbol::ARG_1.ident_string(&env.interns));
set_name(list2.into(), Symbol::ARG_2.ident_string(&env.interns));
let entry = ctx.append_basic_block(parent, "entry");
env.builder.position_at_end(entry);
let return_true = ctx.append_basic_block(parent, "return_true");
let return_false = ctx.append_basic_block(parent, "return_false");
// first, check whether the length is equal
let len1 = list_len(env.builder, list1);
let len2 = list_len(env.builder, list2);
let length_equal: IntValue =
env.builder
.build_int_compare(IntPredicate::EQ, len1, len2, "bounds_check");
let then_block = ctx.append_basic_block(parent, "then");
env.builder
.build_conditional_branch(length_equal, then_block, return_false);
{
// the length is equal; check elements pointwise
env.builder.position_at_end(then_block);
let builder = env.builder;
let element_type =
basic_type_from_layout(env.arena, env.context, element_layout, env.ptr_bytes);
let ptr_type = get_ptr_type(&element_type, AddressSpace::Generic);
let ptr1 = load_list_ptr(env.builder, list1, ptr_type);
let ptr2 = load_list_ptr(env.builder, list2, ptr_type);
// we know that len1 == len2
let end = len1;
// allocate a stack slot for the current index
let index_alloca = builder.build_alloca(ctx.i64_type(), "index");
builder.build_store(index_alloca, ctx.i64_type().const_zero());
let loop_bb = ctx.append_basic_block(parent, "loop");
let body_bb = ctx.append_basic_block(parent, "body");
let increment_bb = ctx.append_basic_block(parent, "increment");
// the "top" of the loop
builder.build_unconditional_branch(loop_bb);
builder.position_at_end(loop_bb);
let curr_index = builder.build_load(index_alloca, "index").into_int_value();
// #index < end
let loop_end_cond =
builder.build_int_compare(IntPredicate::ULT, curr_index, end, "bounds_check");
// if we're at the end, and all elements were equal so far, return true
// otherwise check the current elements for equality
builder.build_conditional_branch(loop_end_cond, body_bb, return_true);
{
// loop body
builder.position_at_end(body_bb);
let elem1 = {
let elem_ptr =
unsafe { builder.build_in_bounds_gep(ptr1, &[curr_index], "load_index") };
builder.build_load(elem_ptr, "get_elem")
};
let elem2 = {
let elem_ptr =
unsafe { builder.build_in_bounds_gep(ptr2, &[curr_index], "load_index") };
builder.build_load(elem_ptr, "get_elem")
};
let are_equal = build_eq(
env,
layout_ids,
elem1,
elem2,
element_layout,
element_layout,
)
.into_int_value();
// if the elements are equal, increment the index and check the next element
// otherwise, return false
builder.build_conditional_branch(are_equal, increment_bb, return_false);
}
{
env.builder.position_at_end(increment_bb);
// constant 1i64
let one = ctx.i64_type().const_int(1, false);
let next_index = builder.build_int_add(curr_index, one, "nextindex");
builder.build_store(index_alloca, next_index);
// jump back to the top of the loop
builder.build_unconditional_branch(loop_bb);
}
}
{
env.builder.position_at_end(return_true);
env.builder
.build_return(Some(&env.context.bool_type().const_int(1, false)));
}
{
env.builder.position_at_end(return_false);
env.builder
.build_return(Some(&env.context.bool_type().const_int(0, false)));
}
}

View file

@ -137,7 +137,10 @@ pub fn basic_type_from_layout<'ctx>(
basic_type_from_record(arena, context, sorted_fields, ptr_bytes) basic_type_from_record(arena, context, sorted_fields, ptr_bytes)
} }
RecursiveUnion(_) | Union(_) => block_of_memory(context, layout, ptr_bytes), RecursiveUnion(_) => block_of_memory(context, layout, ptr_bytes)
.ptr_type(AddressSpace::Generic)
.into(),
Union(_) => block_of_memory(context, layout, ptr_bytes),
RecursivePointer => { RecursivePointer => {
// TODO make this dynamic // TODO make this dynamic
context context
@ -165,6 +168,7 @@ pub fn basic_type_from_builtin<'ctx>(
Int16 => context.i16_type().as_basic_type_enum(), Int16 => context.i16_type().as_basic_type_enum(),
Int8 => context.i8_type().as_basic_type_enum(), Int8 => context.i8_type().as_basic_type_enum(),
Int1 => context.bool_type().as_basic_type_enum(), Int1 => context.bool_type().as_basic_type_enum(),
Usize => ptr_int(context, ptr_bytes).as_basic_type_enum(),
Float128 => context.f128_type().as_basic_type_enum(), Float128 => context.f128_type().as_basic_type_enum(),
Float64 => context.f64_type().as_basic_type_enum(), Float64 => context.f64_type().as_basic_type_enum(),
Float32 => context.f32_type().as_basic_type_enum(), Float32 => context.f32_type().as_basic_type_enum(),

View file

@ -8,6 +8,7 @@ use bumpalo::collections::Vec;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::debug_info::AsDIScope; use inkwell::debug_info::AsDIScope;
use inkwell::module::Linkage; use inkwell::module::Linkage;
use inkwell::types::{AnyTypeEnum, BasicTypeEnum};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue}; use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::{AddressSpace, IntPredicate}; use inkwell::{AddressSpace, IntPredicate};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -342,7 +343,8 @@ pub fn decrement_refcount_layout<'a, 'ctx, 'env>(
} }
RecursiveUnion(tags) => { RecursiveUnion(tags) => {
build_dec_union(env, layout_ids, tags, value); debug_assert!(value.is_pointer_value());
build_dec_rec_union(env, layout_ids, tags, value.into_pointer_value());
} }
FunctionPointer(_, _) | Pointer(_) => {} FunctionPointer(_, _) | Pointer(_) => {}
@ -426,7 +428,8 @@ pub fn increment_refcount_layout<'a, 'ctx, 'env>(
} }
RecursiveUnion(tags) => { RecursiveUnion(tags) => {
build_inc_union(env, layout_ids, tags, value); debug_assert!(value.is_pointer_value());
build_inc_rec_union(env, layout_ids, tags, value.into_pointer_value());
} }
Closure(_, closure_layout, _) => { Closure(_, closure_layout, _) => {
if closure_layout.contains_refcounted() { if closure_layout.contains_refcounted() {
@ -993,12 +996,28 @@ pub fn build_header<'a, 'ctx, 'env>(
fn_name: &str, fn_name: &str,
) -> FunctionValue<'ctx> { ) -> FunctionValue<'ctx> {
let arena = env.arena; let arena = env.arena;
let context = &env.context;
let arg_type = basic_type_from_layout(arena, env.context, &layout, env.ptr_bytes); let arg_type = basic_type_from_layout(arena, env.context, &layout, env.ptr_bytes);
build_header_help(env, fn_name, env.context.void_type().into(), &[arg_type])
}
// inc and dec return void /// Build an increment or decrement function for a specific layout
let fn_type = context.void_type().fn_type(&[arg_type], false); pub fn build_header_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
fn_name: &str,
return_type: AnyTypeEnum<'ctx>,
arguments: &[BasicTypeEnum<'ctx>],
) -> FunctionValue<'ctx> {
use inkwell::types::AnyTypeEnum::*;
let fn_type = match return_type {
ArrayType(t) => t.fn_type(arguments, false),
FloatType(t) => t.fn_type(arguments, false),
FunctionType(_) => unreachable!("functions cannot return functions"),
IntType(t) => t.fn_type(arguments, false),
PointerType(t) => t.fn_type(arguments, false),
StructType(t) => t.fn_type(arguments, false),
VectorType(t) => t.fn_type(arguments, false),
VoidType(t) => t.fn_type(arguments, false),
};
let fn_val = env let fn_val = env
.module .module
@ -1015,6 +1034,202 @@ pub fn build_header<'a, 'ctx, 'env>(
fn_val fn_val
} }
pub fn build_dec_rec_union<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
fields: &'a [&'a [Layout<'a>]],
value: PointerValue<'ctx>,
) {
let layout = Layout::RecursiveUnion(fields);
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let symbol = Symbol::DEC;
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let function_value = build_header(env, &layout, &fn_name);
build_dec_rec_union_help(env, layout_ids, fields, function_value);
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
let call = env
.builder
.build_call(function, &[value.into()], "decrement_union");
call.set_call_convention(FAST_CALL_CONV);
}
pub fn build_dec_rec_union_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
tags: &[&[Layout<'a>]],
fn_val: FunctionValue<'ctx>,
) {
debug_assert!(!tags.is_empty());
use inkwell::types::BasicType;
let context = &env.context;
let builder = env.builder;
// Add a basic block for the entry point
let entry = context.append_basic_block(fn_val, "entry");
builder.position_at_end(entry);
let func_scope = fn_val.get_subprogram().unwrap();
let lexical_block = env.dibuilder.create_lexical_block(
/* scope */ func_scope.as_debug_info_scope(),
/* file */ env.compile_unit.get_file(),
/* line_no */ 0,
/* column_no */ 0,
);
let loc = env.dibuilder.create_debug_location(
context,
/* line */ 0,
/* column */ 0,
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
builder.set_current_debug_location(&context, loc);
// Add args to scope
let arg_symbol = Symbol::ARG_1;
let arg_val = fn_val.get_param_iter().next().unwrap();
set_name(arg_val, arg_symbol.ident_string(&env.interns));
let parent = fn_val;
let layout = Layout::RecursiveUnion(tags);
let before_block = env.builder.get_insert_block().expect("to be in a function");
debug_assert!(arg_val.is_pointer_value());
let value_ptr = arg_val.into_pointer_value();
// next, make a jump table for all possible values of the tag_id
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
let merge_block = env.context.append_basic_block(parent, "decrement_merge");
builder.set_current_debug_location(&context, loc);
for (tag_id, field_layouts) in tags.iter().enumerate() {
// if none of the fields are or contain anything refcounted, just move on
if !field_layouts
.iter()
.any(|x| x.is_refcounted() || x.contains_refcounted())
{
continue;
}
let block = env.context.append_basic_block(parent, "tag_id_decrement");
env.builder.position_at_end(block);
let wrapper_type = basic_type_from_layout(
env.arena,
env.context,
&Layout::Struct(field_layouts),
env.ptr_bytes,
);
// cast the opaque pointer to a pointer of the correct shape
let struct_ptr = cast_basic_basic(
env.builder,
value_ptr.into(),
wrapper_type.ptr_type(AddressSpace::Generic).into(),
)
.into_pointer_value();
for (i, field_layout) in field_layouts.iter().enumerate() {
if let Layout::RecursivePointer = field_layout {
// this field has type `*i64`, but is really a pointer to the data we want
let elem_pointer = env
.builder
.build_struct_gep(struct_ptr, i as u32, "gep_recursive_pointer")
.unwrap();
let ptr_as_i64_ptr = env
.builder
.build_load(elem_pointer, "load_recursive_pointer");
debug_assert!(ptr_as_i64_ptr.is_pointer_value());
// therefore we must cast it to our desired type
let union_type =
basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes);
let recursive_field_ptr = cast_basic_basic(env.builder, ptr_as_i64_ptr, union_type);
// recursively decrement the field
let call = env.builder.build_call(
fn_val,
&[recursive_field_ptr],
"recursive_tag_decrement",
);
// Because it's an internal-only function, use the fast calling convention.
call.set_call_convention(FAST_CALL_CONV);
} else if field_layout.contains_refcounted() {
// TODO this loads the whole field onto the stack;
// that's wasteful if e.g. the field is a big record, where only
// some fields are actually refcounted.
let elem_pointer = env
.builder
.build_struct_gep(struct_ptr, i as u32, "gep_recursive_pointer")
.unwrap();
let field = env
.builder
.build_load(elem_pointer, "decrement_struct_field");
decrement_refcount_layout(env, parent, layout_ids, field, field_layout);
}
}
env.builder.build_unconditional_branch(merge_block);
cases.push((
env.context.i64_type().const_int(tag_id as u64, false),
block,
));
}
cases.reverse();
env.builder.position_at_end(before_block);
// read the tag_id
let current_tag_id = rec_union_read_tag(env, value_ptr);
// switch on it
env.builder
.build_switch(current_tag_id, merge_block, &cases);
env.builder.position_at_end(merge_block);
// decrement this cons-cell itself
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr);
refcount_ptr.decrement(env, &layout);
// this function returns void
builder.build_return(None);
}
pub fn build_dec_union<'a, 'ctx, 'env>( pub fn build_dec_union<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
@ -1061,8 +1276,6 @@ pub fn build_dec_union_help<'a, 'ctx, 'env>(
) { ) {
debug_assert!(!tags.is_empty()); debug_assert!(!tags.is_empty());
use inkwell::types::BasicType;
let context = &env.context; let context = &env.context;
let builder = env.builder; let builder = env.builder;
@ -1088,32 +1301,18 @@ pub fn build_dec_union_help<'a, 'ctx, 'env>(
); );
builder.set_current_debug_location(&context, loc); builder.set_current_debug_location(&context, loc);
let mut scope = Scope::default();
// Add args to scope // Add args to scope
let arg_symbol = Symbol::ARG_1; let arg_symbol = Symbol::ARG_1;
let layout = Layout::Union(tags);
let arg_val = fn_val.get_param_iter().next().unwrap(); let arg_val = fn_val.get_param_iter().next().unwrap();
set_name(arg_val, arg_symbol.ident_string(&env.interns)); set_name(arg_val, arg_symbol.ident_string(&env.interns));
let alloca = create_entry_block_alloca(
env,
fn_val,
arg_val.get_type(),
arg_symbol.ident_string(&env.interns),
);
builder.build_store(alloca, arg_val);
scope.insert(arg_symbol, (layout.clone(), alloca));
let parent = fn_val; let parent = fn_val;
let layout = Layout::RecursiveUnion(tags);
let before_block = env.builder.get_insert_block().expect("to be in a function"); let before_block = env.builder.get_insert_block().expect("to be in a function");
debug_assert!(arg_val.is_struct_value());
let wrapper_struct = arg_val.into_struct_value(); let wrapper_struct = arg_val.into_struct_value();
// next, make a jump table for all possible values of the tag_id // next, make a jump table for all possible values of the tag_id
@ -1147,39 +1346,7 @@ pub fn build_dec_union_help<'a, 'ctx, 'env>(
for (i, field_layout) in field_layouts.iter().enumerate() { for (i, field_layout) in field_layouts.iter().enumerate() {
if let Layout::RecursivePointer = field_layout { if let Layout::RecursivePointer = field_layout {
// this field has type `*i64`, but is really a pointer to the data we want panic!("a non-recursive tag union cannot contain RecursivePointer");
let ptr_as_i64_ptr = env
.builder
.build_extract_value(wrapper_struct, i as u32, "decrement_struct_field")
.unwrap();
debug_assert!(ptr_as_i64_ptr.is_pointer_value());
// therefore we must cast it to our desired type
let union_type = block_of_memory(env.context, &layout, env.ptr_bytes);
let recursive_field_ptr = cast_basic_basic(
env.builder,
ptr_as_i64_ptr,
union_type.ptr_type(AddressSpace::Generic).into(),
)
.into_pointer_value();
let recursive_field = env
.builder
.build_load(recursive_field_ptr, "load_recursive_field");
// recursively decrement the field
let call =
env.builder
.build_call(fn_val, &[recursive_field], "recursive_tag_decrement");
// Because it's an internal-only function, use the fast calling convention.
call.set_call_convention(FAST_CALL_CONV);
// TODO do this decrement before the recursive call?
// Then the recursive call is potentially TCE'd
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, recursive_field_ptr);
refcount_ptr.decrement(env, &layout);
} else if field_layout.contains_refcounted() { } else if field_layout.contains_refcounted() {
let field_ptr = env let field_ptr = env
.builder .builder
@ -1227,6 +1394,213 @@ pub fn build_dec_union_help<'a, 'ctx, 'env>(
builder.build_return(None); builder.build_return(None);
} }
pub fn build_inc_rec_union<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
fields: &'a [&'a [Layout<'a>]],
value: PointerValue<'ctx>,
) {
let layout = Layout::RecursiveUnion(fields);
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let symbol = Symbol::INC;
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let function_value = build_header(env, &layout, &fn_name);
build_inc_rec_union_help(env, layout_ids, fields, function_value);
function_value
}
};
env.builder.position_at_end(block);
env.builder
.set_current_debug_location(env.context, di_location);
let call = env
.builder
.build_call(function, &[value.into()], "increment_union");
call.set_call_convention(FAST_CALL_CONV);
}
fn rec_union_read_tag<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
value_ptr: PointerValue<'ctx>,
) -> IntValue<'ctx> {
// Assumption: the tag is the first thing stored
// so cast the pointer to the data to a `i64*`
let tag_ptr = cast_basic_basic(
env.builder,
value_ptr.into(),
env.context
.i64_type()
.ptr_type(AddressSpace::Generic)
.into(),
)
.into_pointer_value();
env.builder
.build_load(tag_ptr, "load_tag_id")
.into_int_value()
}
pub fn build_inc_rec_union_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
tags: &[&[Layout<'a>]],
fn_val: FunctionValue<'ctx>,
) {
debug_assert!(!tags.is_empty());
use inkwell::types::BasicType;
let context = &env.context;
let builder = env.builder;
// Add a basic block for the entry point
let entry = context.append_basic_block(fn_val, "entry");
builder.position_at_end(entry);
let func_scope = fn_val.get_subprogram().unwrap();
let lexical_block = env.dibuilder.create_lexical_block(
/* scope */ func_scope.as_debug_info_scope(),
/* file */ env.compile_unit.get_file(),
/* line_no */ 0,
/* column_no */ 0,
);
let loc = env.dibuilder.create_debug_location(
context,
/* line */ 0,
/* column */ 0,
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
builder.set_current_debug_location(&context, loc);
// Add args to scope
let arg_symbol = Symbol::ARG_1;
let arg_val = fn_val.get_param_iter().next().unwrap();
set_name(arg_val, arg_symbol.ident_string(&env.interns));
let parent = fn_val;
let layout = Layout::RecursiveUnion(tags);
let before_block = env.builder.get_insert_block().expect("to be in a function");
debug_assert!(arg_val.is_pointer_value());
let value_ptr = arg_val.into_pointer_value();
// read the tag_id
let tag_id = rec_union_read_tag(env, value_ptr);
let tag_id_u8 = cast_basic_basic(env.builder, tag_id.into(), env.context.i8_type().into());
// next, make a jump table for all possible values of the tag_id
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
let merge_block = env.context.append_basic_block(parent, "increment_merge");
for (tag_id, field_layouts) in tags.iter().enumerate() {
// if none of the fields are or contain anything refcounted, just move on
if !field_layouts
.iter()
.any(|x| x.is_refcounted() || x.contains_refcounted())
{
continue;
}
let block = env.context.append_basic_block(parent, "tag_id_increment");
env.builder.position_at_end(block);
let wrapper_type = basic_type_from_layout(
env.arena,
env.context,
&Layout::Struct(field_layouts),
env.ptr_bytes,
);
// cast the opaque pointer to a pointer of the correct shape
let struct_ptr = cast_basic_basic(
env.builder,
value_ptr.into(),
wrapper_type.ptr_type(AddressSpace::Generic).into(),
)
.into_pointer_value();
for (i, field_layout) in field_layouts.iter().enumerate() {
if let Layout::RecursivePointer = field_layout {
// this field has type `*i64`, but is really a pointer to the data we want
let elem_pointer = env
.builder
.build_struct_gep(struct_ptr, i as u32, "gep_recursive_pointer")
.unwrap();
let ptr_as_i64_ptr = env
.builder
.build_load(elem_pointer, "load_recursive_pointer");
debug_assert!(ptr_as_i64_ptr.is_pointer_value());
// therefore we must cast it to our desired type
let union_type = block_of_memory(env.context, &layout, env.ptr_bytes);
let recursive_field_ptr = cast_basic_basic(
env.builder,
ptr_as_i64_ptr,
union_type.ptr_type(AddressSpace::Generic).into(),
);
// recursively increment the field
let call = env.builder.build_call(
fn_val,
&[recursive_field_ptr],
"recursive_tag_increment",
);
// Because it's an internal-only function, use the fast calling convention.
call.set_call_convention(FAST_CALL_CONV);
} else if field_layout.contains_refcounted() {
let elem_pointer = env
.builder
.build_struct_gep(struct_ptr, i as u32, "gep_field")
.unwrap();
let field = env.builder.build_load(elem_pointer, "load_field");
increment_refcount_layout(env, parent, layout_ids, field, field_layout);
}
}
env.builder.build_unconditional_branch(merge_block);
cases.push((env.context.i8_type().const_int(tag_id as u64, false), block));
}
env.builder.position_at_end(before_block);
env.builder
.build_switch(tag_id_u8.into_int_value(), merge_block, &cases);
env.builder.position_at_end(merge_block);
// increment this cons cell
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr);
refcount_ptr.increment(env);
// this function returns void
builder.build_return(None);
}
pub fn build_inc_union<'a, 'ctx, 'env>( pub fn build_inc_union<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
@ -1299,26 +1673,12 @@ pub fn build_inc_union_help<'a, 'ctx, 'env>(
); );
builder.set_current_debug_location(&context, loc); builder.set_current_debug_location(&context, loc);
let mut scope = Scope::default();
// Add args to scope // Add args to scope
let arg_symbol = Symbol::ARG_1; let arg_symbol = Symbol::ARG_1;
let layout = Layout::Union(tags);
let arg_val = fn_val.get_param_iter().next().unwrap(); let arg_val = fn_val.get_param_iter().next().unwrap();
set_name(arg_val, arg_symbol.ident_string(&env.interns)); set_name(arg_val, arg_symbol.ident_string(&env.interns));
let alloca = create_entry_block_alloca(
env,
fn_val,
arg_val.get_type(),
arg_symbol.ident_string(&env.interns),
);
builder.build_store(alloca, arg_val);
scope.insert(arg_symbol, (layout.clone(), alloca));
let parent = fn_val; let parent = fn_val;
let layout = Layout::RecursiveUnion(tags); let layout = Layout::RecursiveUnion(tags);

View file

@ -415,25 +415,21 @@ mod gen_list {
); );
} }
// #[test]
// "panicked at 'not yet implemented: Handle equals for builtin layouts Str == Str'" fn list_keep_if_str_is_hello() {
// assert_evals_to!(
// #[test] indoc!(
// fn list_keep_if_str_is_hello() { r#"
// assert_evals_to!( List.keepIf ["x", "y", "x"] (\x -> x == "x")
// indoc!( "#
// r#" ),
// strIsHello : Str -> Bool RocList::from_slice(&[
// strIsHello = \str -> RocStr::from_slice("x".as_bytes()),
// str == "Hello" RocStr::from_slice("x".as_bytes())
// ]),
// List.keepIf ["Hello", "Hello", "Goodbye"] strIsHello RocList<RocStr>
// "# );
// ), }
// RocList::from_slice(&["Hello", "Hello"]),
// RocList<&'static str>
// );
// }
#[test] #[test]
fn list_map_on_empty_list_with_int_layout() { fn list_map_on_empty_list_with_int_layout() {
@ -1277,7 +1273,7 @@ mod gen_list {
app "quicksort" provides [ main ] to "./platform" app "quicksort" provides [ main ] to "./platform"
swap : I64, I64, List a -> List a swap : Nat, Nat, List a -> List a
swap = \i, j, list -> swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) -> Pair (Ok atI) (Ok atJ) ->
@ -1400,7 +1396,7 @@ mod gen_list {
quicksortHelp list 0 (n - 1) quicksortHelp list 0 (n - 1)
quicksortHelp : List (Num a), I64, I64 -> List (Num a) quicksortHelp : List (Num a), Nat, Nat -> List (Num a)
quicksortHelp = \list, low, high -> quicksortHelp = \list, low, high ->
if low < high then if low < high then
when partition low high list is when partition low high list is
@ -1412,7 +1408,7 @@ mod gen_list {
list list
swap : I64, I64, List a -> List a swap : Nat, Nat, List a -> List a
swap = \i, j, list -> swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) -> Pair (Ok atI) (Ok atJ) ->
@ -1423,7 +1419,7 @@ mod gen_list {
_ -> _ ->
[] []
partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ] partition : Nat, Nat, List (Num a) -> [ Pair Nat (List (Num a)) ]
partition = \low, high, initialList -> partition = \low, high, initialList ->
when List.get initialList high is when List.get initialList high is
Ok pivot -> Ok pivot ->
@ -1435,7 +1431,7 @@ mod gen_list {
Pair (low - 1) initialList Pair (low - 1) initialList
partitionHelp : I64, I64, List (Num a), I64, (Num a) -> [ Pair I64 (List (Num a)) ] partitionHelp : Nat, Nat, List (Num a), Nat, (Num a) -> [ Pair Nat (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot -> partitionHelp = \i, j, list, high, pivot ->
if j < high then if j < high then
when List.get list j is when List.get list j is
@ -1470,7 +1466,7 @@ mod gen_list {
quicksortHelp list 0 (List.len list - 1) quicksortHelp list 0 (List.len list - 1)
quicksortHelp : List (Num a), I64, I64 -> List (Num a) quicksortHelp : List (Num a), Nat, Nat -> List (Num a)
quicksortHelp = \list, low, high -> quicksortHelp = \list, low, high ->
if low < high then if low < high then
when partition low high list is when partition low high list is
@ -1482,7 +1478,7 @@ mod gen_list {
list list
swap : I64, I64, List a -> List a swap : Nat, Nat, List a -> List a
swap = \i, j, list -> swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) -> Pair (Ok atI) (Ok atJ) ->
@ -1493,7 +1489,7 @@ mod gen_list {
_ -> _ ->
[] []
partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ] partition : Nat, Nat, List (Num a) -> [ Pair Nat (List (Num a)) ]
partition = \low, high, initialList -> partition = \low, high, initialList ->
when List.get initialList high is when List.get initialList high is
Ok pivot -> Ok pivot ->
@ -1505,7 +1501,7 @@ mod gen_list {
Pair (low - 1) initialList Pair (low - 1) initialList
partitionHelp : I64, I64, List (Num a), I64, Num a -> [ Pair I64 (List (Num a)) ] partitionHelp : Nat, Nat, List (Num a), Nat, Num a -> [ Pair Nat (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot -> partitionHelp = \i, j, list, high, pivot ->
# if j < high then # if j < high then
if False then if False then
@ -1543,7 +1539,7 @@ mod gen_list {
quicksortHelp list 0 (List.len list - 1) quicksortHelp list 0 (List.len list - 1)
quicksortHelp : List (Num a), I64, I64 -> List (Num a) quicksortHelp : List (Num a), Nat, Nat -> List (Num a)
quicksortHelp = \list, low, high -> quicksortHelp = \list, low, high ->
if low < high then if low < high then
when partition low high list is when partition low high list is
@ -1555,7 +1551,7 @@ mod gen_list {
list list
swap : I64, I64, List a -> List a swap : Nat, Nat, List a -> List a
swap = \i, j, list -> swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) -> Pair (Ok atI) (Ok atJ) ->
@ -1566,7 +1562,7 @@ mod gen_list {
_ -> _ ->
[] []
partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ] partition : Nat, Nat, List (Num a) -> [ Pair Nat (List (Num a)) ]
partition = \low, high, initialList -> partition = \low, high, initialList ->
when List.get initialList high is when List.get initialList high is
Ok pivot -> Ok pivot ->
@ -1578,7 +1574,7 @@ mod gen_list {
Pair (low - 1) initialList Pair (low - 1) initialList
partitionHelp : I64, I64, List (Num a), I64, Num a -> [ Pair I64 (List (Num a)) ] partitionHelp : Nat, Nat, List (Num a), Nat, Num a -> [ Pair Nat (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot -> partitionHelp = \i, j, list, high, pivot ->
if j < high then if j < high then
when List.get list j is when List.get list j is
@ -1707,4 +1703,55 @@ mod gen_list {
assert_evals_to!("List.sum [ 1, 2, 3 ]", 6, i64); assert_evals_to!("List.sum [ 1, 2, 3 ]", 6, i64);
assert_evals_to!("List.sum [ 1.1, 2.2, 3.3 ]", 6.6, f64); assert_evals_to!("List.sum [ 1.1, 2.2, 3.3 ]", 6.6, f64);
} }
#[test]
fn list_eq_empty() {
assert_evals_to!("[] == []", true, bool);
assert_evals_to!("[] != []", false, bool);
}
#[test]
fn list_eq_by_length() {
assert_evals_to!("[1] == []", false, bool);
assert_evals_to!("[] == [1]", false, bool);
}
#[test]
fn list_eq_compare_pointwise() {
assert_evals_to!("[1] == [1]", true, bool);
assert_evals_to!("[2] == [1]", false, bool);
}
#[test]
fn list_eq_nested() {
assert_evals_to!("[[1]] == [[1]]", true, bool);
assert_evals_to!("[[2]] == [[1]]", false, bool);
}
#[test]
fn list_neq_compare_pointwise() {
assert_evals_to!("[1] != [1]", false, bool);
assert_evals_to!("[2] != [1]", true, bool);
}
#[test]
fn list_neq_nested() {
assert_evals_to!("[[1]] != [[1]]", false, bool);
assert_evals_to!("[[2]] != [[1]]", true, bool);
}
#[test]
#[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)]
fn cleanup_because_exception() {
assert_evals_to!(
indoc!(
r#"
x = [ 1,2 ]
5 + Num.maxInt + 3 + List.len x
"#
),
RocList::from_slice(&[false; 1]),
RocList<bool>
);
}
} }

View file

@ -15,6 +15,357 @@ mod helpers;
mod gen_num { mod gen_num {
use roc_std::RocOrder; use roc_std::RocOrder;
#[test]
fn nat_alias() {
assert_evals_to!(
indoc!(
r#"
i : Nat
i = 1
i
"#
),
1,
usize
);
}
#[test]
fn i128_signed_int_alias() {
assert_evals_to!(
indoc!(
r#"
i : I128
i = 128
i
"#
),
128,
i128
);
}
#[test]
fn i64_signed_int_alias() {
assert_evals_to!(
indoc!(
r#"
i : I64
i = 64
i
"#
),
64,
i64
);
}
#[test]
fn i32_signed_int_alias() {
assert_evals_to!(
indoc!(
r#"
i : I32
i = 32
i
"#
),
32,
i32
);
}
#[test]
fn i16_signed_int_alias() {
assert_evals_to!(
indoc!(
r#"
i : I16
i = 16
i
"#
),
16,
i16
);
}
#[test]
fn i8_signed_int_alias() {
assert_evals_to!(
indoc!(
r#"
i : I8
i = 8
i
"#
),
8,
i8
);
}
#[test]
fn i128_hex_int_alias() {
assert_evals_to!(
indoc!(
r#"
f : I128
f = 0x123
f
"#
),
0x123,
i128
);
}
#[test]
fn i64_hex_int_alias() {
assert_evals_to!(
indoc!(
r#"
f : I64
f = 0x123
f
"#
),
0x123,
i64
);
}
#[test]
fn i32_hex_int_alias() {
assert_evals_to!(
indoc!(
r#"
f : I32
f = 0x123
f
"#
),
0x123,
i32
);
}
#[test]
fn i16_hex_int_alias() {
assert_evals_to!(
indoc!(
r#"
f : I16
f = 0x123
f
"#
),
0x123,
i16
);
}
#[test]
fn i8_hex_int_alias() {
assert_evals_to!(
indoc!(
r#"
f : I8
f = 0xA
f
"#
),
0xA,
i8
);
}
#[test]
fn u128_signed_int_alias() {
assert_evals_to!(
indoc!(
r#"
i : U128
i = 128
i
"#
),
128,
u128
);
}
#[test]
fn u64_signed_int_alias() {
assert_evals_to!(
indoc!(
r#"
i : U64
i = 64
i
"#
),
64,
u64
);
}
#[test]
fn u32_signed_int_alias() {
assert_evals_to!(
indoc!(
r#"
i : U32
i = 32
i
"#
),
32,
u32
);
}
#[test]
fn u16_signed_int_alias() {
assert_evals_to!(
indoc!(
r#"
i : U16
i = 16
i
"#
),
16,
u16
);
}
#[test]
fn u8_signed_int_alias() {
assert_evals_to!(
indoc!(
r#"
i : U8
i = 8
i
"#
),
8,
u8
);
}
#[test]
fn u128_hex_int_alias() {
assert_evals_to!(
indoc!(
r#"
f : U128
f = 0x123
f
"#
),
0x123,
i128
);
}
#[test]
fn u64_hex_int_alias() {
assert_evals_to!(
indoc!(
r#"
f : U64
f = 0x123
f
"#
),
0x123,
u64
);
}
#[test]
fn u32_hex_int_alias() {
assert_evals_to!(
indoc!(
r#"
f : U32
f = 0x123
f
"#
),
0x123,
u32
);
}
#[test]
fn u16_hex_int_alias() {
assert_evals_to!(
indoc!(
r#"
f : U16
f = 0x123
f
"#
),
0x123,
u16
);
}
#[test]
fn u8_hex_int_alias() {
assert_evals_to!(
indoc!(
r#"
f : U8
f = 0xA
f
"#
),
0xA,
u8
);
}
#[test]
fn f64_float_alias() {
assert_evals_to!(
indoc!(
r#"
f : F64
f = 3.6
f
"#
),
3.6,
f64
);
}
#[test]
fn f32_float_alias() {
assert_evals_to!(
indoc!(
r#"
f : F32
f = 3.6
f
"#
),
3.6,
f32
);
}
#[test] #[test]
fn f64_sqrt() { fn f64_sqrt() {
// FIXME this works with normal types, but fails when checking uniqueness types // FIXME this works with normal types, but fails when checking uniqueness types
@ -406,6 +757,14 @@ mod gen_num {
assert_evals_to!("Num.bitwiseAnd 200 0", 0, i64); assert_evals_to!("Num.bitwiseAnd 200 0", 0, i64);
} }
#[test]
fn bitwise_xor() {
assert_evals_to!("Num.bitwiseXor 20 20", 0, i64);
assert_evals_to!("Num.bitwiseXor 15 14", 1, i64);
assert_evals_to!("Num.bitwiseXor 7 15", 8, i64);
assert_evals_to!("Num.bitwiseXor 200 0", 200, i64);
}
#[test] #[test]
fn lt_i64() { fn lt_i64() {
assert_evals_to!("1 < 2", true, bool); assert_evals_to!("1 < 2", true, bool);

View file

@ -135,7 +135,7 @@ mod gen_primitives {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
x : [ Pair I64 I64 ] x : [ Pair (Int *) (Int *) ]
x = Pair 0x2 0x3 x = Pair 0x2 0x3
when x is when x is
@ -152,7 +152,7 @@ mod gen_primitives {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
x : [A I64, B I64] x : [A (Int *), B (Int *)]
x = A 0x2 x = A 0x2
when x is when x is
@ -170,7 +170,7 @@ mod gen_primitives {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
x : [A I64, B I64] x : [A (Int *), B (Int *)]
x = B 0x3 x = B 0x3
when x is when x is
@ -293,7 +293,7 @@ mod gen_primitives {
indoc!( indoc!(
r#" r#"
wrapper = \{} -> wrapper = \{} ->
alwaysFloatIdentity : I64 -> (F64 -> F64) alwaysFloatIdentity : Int * -> (Float * -> Float *)
alwaysFloatIdentity = \_ -> alwaysFloatIdentity = \_ ->
(\a -> a) (\a -> a)
@ -557,14 +557,14 @@ mod gen_primitives {
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
len : LinkedList a -> I64 len : LinkedList a -> Int *
len = \list -> len = \list ->
when list is when list is
Nil -> 0 Nil -> 0
Cons _ rest -> 1 + len rest Cons _ rest -> 1 + len rest
main = main =
nil : LinkedList I64 nil : LinkedList {}
nil = Nil nil = Nil
len nil len nil
@ -584,10 +584,10 @@ mod gen_primitives {
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
nil : LinkedList I64 nil : LinkedList (Int *)
nil = Nil nil = Nil
length : LinkedList a -> I64 length : LinkedList a -> Int *
length = \list -> length = \list ->
when list is when list is
Nil -> 0 Nil -> 0
@ -611,10 +611,10 @@ mod gen_primitives {
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
one : LinkedList I64 one : LinkedList (Int *)
one = Cons 1 Nil one = Cons 1 Nil
length : LinkedList a -> I64 length : LinkedList a -> Int *
length = \list -> length = \list ->
when list is when list is
Nil -> 0 Nil -> 0
@ -638,10 +638,10 @@ mod gen_primitives {
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
one : LinkedList I64 one : LinkedList (Int *)
one = Cons 1 Nil one = Cons 1 Nil
length : LinkedList a -> I64 length : LinkedList a -> Int *
length = \list -> length = \list ->
when list is when list is
Nil -> 0 Nil -> 0
@ -665,10 +665,10 @@ mod gen_primitives {
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
three : LinkedList I64 three : LinkedList (Int *)
three = Cons 3 (Cons 2 (Cons 1 Nil)) three = Cons 3 (Cons 2 (Cons 1 Nil))
length : LinkedList a -> I64 length : LinkedList a -> Int *
length = \list -> length = \list ->
when list is when list is
Nil -> 0 Nil -> 0
@ -693,7 +693,7 @@ mod gen_primitives {
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
three : LinkedList I64 three : LinkedList (Int *)
three = Cons 3 (Cons 2 (Cons 1 Nil)) three = Cons 3 (Cons 2 (Cons 1 Nil))
@ -721,10 +721,10 @@ mod gen_primitives {
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
zero : LinkedList I64 zero : LinkedList (Int *)
zero = Nil zero = Nil
sum : LinkedList I64 -> I64 sum : LinkedList (Int *) -> Int *
sum = \list -> sum = \list ->
when list is when list is
Nil -> 0 Nil -> 0
@ -748,7 +748,7 @@ mod gen_primitives {
LinkedList a : [ Nil, Cons a (LinkedList a) ] LinkedList a : [ Nil, Cons a (LinkedList a) ]
three : LinkedList I64 three : LinkedList (Int *)
three = Cons 3 (Cons 2 (Cons 1 Nil)) three = Cons 3 (Cons 2 (Cons 1 Nil))
sum : LinkedList (Num a) -> Num a sum : LinkedList (Num a) -> Num a
@ -779,7 +779,7 @@ mod gen_primitives {
r#" r#"
Maybe a : [ Nothing, Just a ] Maybe a : [ Nothing, Just a ]
x : Maybe (Maybe I64) x : Maybe (Maybe (Int *))
x = Just (Just 41) x = Just (Just 41)
when x is when x is
@ -796,7 +796,7 @@ mod gen_primitives {
r#" r#"
Maybe a : [ Nothing, Just a ] Maybe a : [ Nothing, Just a ]
x : Maybe (Maybe I64) x : Maybe (Maybe (Int *))
x = Just Nothing x = Just Nothing
when x is when x is
@ -814,7 +814,7 @@ mod gen_primitives {
r#" r#"
Maybe a : [ Nothing, Just a ] Maybe a : [ Nothing, Just a ]
x : Maybe (Maybe I64) x : Maybe (Maybe (Int *))
x = Nothing x = Nothing
when x is when x is
@ -908,7 +908,7 @@ mod gen_primitives {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
foo : I64 foo : Int *
foo foo
@ -1033,11 +1033,11 @@ mod gen_primitives {
runEffect : Effect a -> a runEffect : Effect a -> a
runEffect = \@Effect thunk -> thunk {} runEffect = \@Effect thunk -> thunk {}
foo : Effect F64 foo : Effect (Float *)
foo = foo =
succeed 3.14 succeed 3.14
main : F64 main : Float *
main = main =
runEffect foo runEffect foo
@ -1058,14 +1058,14 @@ mod gen_primitives {
# succeed : a -> ({} -> a) # succeed : a -> ({} -> a)
succeed = \x -> \{} -> x succeed = \x -> \{} -> x
foo : {} -> F64 foo : {} -> Float *
foo = foo =
succeed 3.14 succeed 3.14
# runEffect : ({} -> a) -> a # runEffect : ({} -> a) -> a
runEffect = \thunk -> thunk {} runEffect = \thunk -> thunk {}
main : F64 main : Float *
main = main =
runEffect foo runEffect foo
"# "#
@ -1122,6 +1122,40 @@ mod gen_primitives {
); );
} }
#[test]
fn linked_list_is_singleton() {
assert_non_opt_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
ConsList a : [ Cons a (ConsList a), Nil ]
empty : ConsList a
empty = Nil
isSingleton : ConsList a -> Bool
isSingleton = \list ->
when list is
Cons _ Nil ->
True
_ ->
False
main : Bool
main =
myList : ConsList I64
myList = empty
isSingleton myList
"#
),
false,
bool
);
}
#[test] #[test]
fn linked_list_is_empty_1() { fn linked_list_is_empty_1() {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
@ -1145,7 +1179,7 @@ mod gen_primitives {
main : Bool main : Bool
main = main =
myList : ConsList I64 myList : ConsList (Int *)
myList = empty myList = empty
isEmpty myList isEmpty myList
@ -1187,6 +1221,26 @@ mod gen_primitives {
); );
} }
#[test]
fn linked_list_singleton() {
// verifies only that valid llvm is produced
assert_non_opt_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
ConsList a : [ Cons a (ConsList a), Nil ]
main : ConsList I64
main = Cons 0x1 Nil
"#
),
0,
i64,
|_| 0
);
}
#[test] #[test]
fn recursive_functon_with_rigid() { fn recursive_functon_with_rigid() {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
@ -1194,16 +1248,16 @@ mod gen_primitives {
r#" r#"
app "test" provides [ main ] to "./platform" app "test" provides [ main ] to "./platform"
State a : { count : I64, x : a } State a : { count : Int *, x : a }
foo : State a -> I64 foo : State a -> Int *
foo = \state -> foo = \state ->
if state.count == 0 then if state.count == 0 then
0 0
else else
1 + foo { count: state.count - 1, x: state.x } 1 + foo { count: state.count - 1, x: state.x }
main : I64 main : Int *
main = main =
foo { count: 3, x: {} } foo { count: 3, x: {} }
"# "#
@ -1284,7 +1338,7 @@ mod gen_primitives {
_ -> _ ->
Node color key value left right Node color key value left right
main : RedBlackTree I64 {} main : RedBlackTree (Int *) {}
main = main =
insert 0 {} Empty insert 0 {} Empty
"# "#
@ -1325,7 +1379,7 @@ mod gen_primitives {
_ -> _ ->
Empty Empty
main : RedBlackTree I64 main : RedBlackTree (Int *)
main = main =
balance Red 0 Empty Empty balance Red 0 Empty Empty
"# "#
@ -1348,13 +1402,14 @@ mod gen_primitives {
balance = \key, left -> balance = \key, left ->
Node key left Empty Node key left Empty
main : RedBlackTree I64 main : RedBlackTree (Int *)
main = main =
balance 0 Empty balance 0 Empty
"# "#
), ),
1, 1,
i64 &i64,
|x: &i64| *x
); );
} }
@ -1395,7 +1450,7 @@ mod gen_primitives {
_ -> _ ->
Empty Empty
main : RedBlackTree I64 I64 main : RedBlackTree (Int *) (Int *)
main = main =
balance Red 0 0 Empty Empty balance Red 0 0 Empty Empty
"# "#
@ -1445,13 +1500,14 @@ mod gen_primitives {
_ -> _ ->
Node color key value left right Node color key value left right
main : RedBlackTree I64 I64 main : RedBlackTree (Int *) (Int *)
main = main =
balance Red 0 0 Empty Empty balance Red 0 0 Empty Empty
"# "#
), ),
1, 1,
i64 &i64,
|x: &i64| *x
); );
} }
@ -1465,7 +1521,7 @@ mod gen_primitives {
ConsList a : [ Cons a (ConsList a), Nil ] ConsList a : [ Cons a (ConsList a), Nil ]
balance : ConsList I64 -> I64 balance : ConsList (Int *) -> Int *
balance = \right -> balance = \right ->
when right is when right is
Cons 1 foo -> Cons 1 foo ->
@ -1474,7 +1530,7 @@ mod gen_primitives {
_ -> 3 _ -> 3
_ -> 3 _ -> 3
main : I64 main : Int *
main = main =
when balance Nil is when balance Nil is
_ -> 3 _ -> 3
@ -1491,13 +1547,13 @@ mod gen_primitives {
ConsList a : [ Cons a (ConsList a), Nil ] ConsList a : [ Cons a (ConsList a), Nil ]
balance : ConsList I64 -> I64 balance : ConsList (Int *) -> Int *
balance = \right -> balance = \right ->
when right is when right is
Cons 1 (Cons 1 _) -> 3 Cons 1 (Cons 1 _) -> 3
_ -> 3 _ -> 3
main : I64 main : Int *
main = main =
when balance Nil is when balance Nil is
_ -> 3 _ -> 3
@ -1519,7 +1575,7 @@ mod gen_primitives {
ConsList a : [ Cons a (ConsList a), Nil ] ConsList a : [ Cons a (ConsList a), Nil ]
balance : ConsList I64 -> I64 balance : ConsList (Int *) -> Int *
balance = \right -> balance = \right ->
when right is when right is
Cons 1 foo -> Cons 1 foo ->
@ -1528,7 +1584,7 @@ mod gen_primitives {
_ -> 3 _ -> 3
_ -> 3 _ -> 3
main : I64 main : Int *
main = main =
when balance Nil is when balance Nil is
_ -> 3 _ -> 3
@ -1548,13 +1604,13 @@ mod gen_primitives {
ConsList a : [ Cons a (ConsList a), Nil ] ConsList a : [ Cons a (ConsList a), Nil ]
foo : ConsList I64 -> I64 foo : ConsList (Int *) -> Int *
foo = \list -> foo = \list ->
when list is when list is
Cons _ (Cons x _) -> x Cons _ (Cons x _) -> x
_ -> 0 _ -> 0
main : I64 main : Int *
main = main =
foo (Cons 1 (Cons 32 Nil)) foo (Cons 1 (Cons 32 Nil))
"# "#
@ -1571,15 +1627,15 @@ mod gen_primitives {
r#" r#"
app "test" provides [ main ] to "./platform" app "test" provides [ main ] to "./platform"
BTree : [ Node BTree BTree, Leaf I64 ] BTree : [ Node BTree BTree, Leaf (Int *) ]
foo : BTree -> I64 foo : BTree -> Int *
foo = \btree -> foo = \btree ->
when btree is when btree is
Node (Node (Leaf x) _) _ -> x Node (Node (Leaf x) _) _ -> x
_ -> 0 _ -> 0
main : I64 main : Int *
main = main =
foo (Node (Node (Leaf 32) (Leaf 0)) (Leaf 0)) foo (Node (Node (Leaf 32) (Leaf 0)) (Leaf 0))
"# "#
@ -1603,7 +1659,7 @@ mod gen_primitives {
A -> (\_ -> 3.14) A -> (\_ -> 3.14)
B -> (\_ -> 3.14) B -> (\_ -> 3.14)
main : F64 main : Float *
main = main =
(foo {}) 0 (foo {}) 0
"# "#
@ -1646,7 +1702,7 @@ mod gen_primitives {
Ok x -> transform x Ok x -> transform x
Err e -> fail e Err e -> fail e
main : Task {} F64 main : Task {} (Float *)
main = after (always "foo") (\_ -> always {}) main = after (always "foo") (\_ -> always {})
"# "#
@ -1676,7 +1732,7 @@ mod gen_primitives {
@Effect inner @Effect inner
main : Task {} F64 main : Task {} (Float *)
main = always {} main = always {}
"# "#
), ),
@ -1707,7 +1763,7 @@ mod gen_primitives {
Task a err : Effect (Result a err) Task a err : Effect (Result a err)
always : a -> Task a F64 always : a -> Task a (Float *)
always = \x -> effectAlways (Ok x) always = \x -> effectAlways (Ok x)
# the problem is that this restricts to `Task {} *` # the problem is that this restricts to `Task {} *`
@ -1722,7 +1778,7 @@ mod gen_primitives {
# but here it must be `forall b. Task b {}` # but here it must be `forall b. Task b {}`
Err e -> fail e Err e -> fail e
main : Task {} F64 main : Task {} (Float *)
main = main =
after (always "foo") (\_ -> always {}) after (always "foo") (\_ -> always {})
"# "#
@ -1774,7 +1830,7 @@ mod gen_primitives {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
x : Result I64 F64 x : Result (Int *) (Float *)
x = Ok 4 x = Ok 4
(Ok y) = x (Ok y) = x
@ -1803,4 +1859,49 @@ mod gen_primitives {
i64 i64
); );
} }
#[test]
fn case_or_pattern() {
// the `0` branch body should only be generated once in the future
// it is currently duplicated
assert_evals_to!(
indoc!(
r#"
x : [ Red, Green, Blue ]
x = Red
when x is
Red | Green -> 0
Blue -> 1
"#
),
0,
i64
);
}
#[test]
fn case_jump() {
// the decision tree will generate a jump to the `1` branch here
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
ConsList a : [ Cons a (ConsList a), Nil ]
x : ConsList I64
x = Nil
main =
when Pair x x is
Pair Nil _ -> 1
Pair _ Nil -> 2
Pair (Cons a _) (Cons b _) -> a + b + 3
"#
),
1,
i64
);
}
} }

View file

@ -514,4 +514,28 @@ mod gen_str {
let min = format!("{}", i64::MIN); let min = format!("{}", i64::MIN);
assert_evals_to!(r#"Str.fromInt Num.minInt"#, &min, &'static str); assert_evals_to!(r#"Str.fromInt Num.minInt"#, &min, &'static str);
} }
#[test]
fn str_equality() {
assert_evals_to!(r#""a" == "a""#, true, bool);
assert_evals_to!(
r#""loremipsumdolarsitamet" == "loremipsumdolarsitamet""#,
true,
bool
);
assert_evals_to!(r#""a" != "b""#, true, bool);
assert_evals_to!(r#""a" == "b""#, false, bool);
}
#[test]
fn str_clone() {
use roc_std::RocStr;
let long = RocStr::from_slice("loremipsumdolarsitamet".as_bytes());
let short = RocStr::from_slice("x".as_bytes());
let empty = RocStr::from_slice("".as_bytes());
debug_assert_eq!(long.clone(), long);
debug_assert_eq!(short.clone(), short);
debug_assert_eq!(empty.clone(), empty);
}
} }

View file

@ -19,7 +19,7 @@ fn promote_expr_to_module(src: &str) -> String {
pub fn helper<'a>( pub fn helper<'a>(
arena: &'a bumpalo::Bump, arena: &'a bumpalo::Bump,
src: &str, src: &str,
stdlib: roc_builtins::std::StdLib, stdlib: &'a roc_builtins::std::StdLib,
leak: bool, leak: bool,
context: &'a inkwell::context::Context, context: &'a inkwell::context::Context,
) -> (&'static str, String, Library) { ) -> (&'static str, String, Library) {
@ -41,6 +41,9 @@ pub fn helper<'a>(
module_src = &temp; module_src = &temp;
} }
let target = target_lexicon::Triple::host();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let exposed_types = MutMap::default(); let exposed_types = MutMap::default();
let loaded = roc_load::file::load_and_monomorphize_from_str( let loaded = roc_load::file::load_and_monomorphize_from_str(
arena, arena,
@ -49,6 +52,7 @@ pub fn helper<'a>(
stdlib, stdlib,
src_dir, src_dir,
exposed_types, exposed_types,
ptr_bytes,
); );
let mut loaded = loaded.expect("failed to load module"); let mut loaded = loaded.expect("failed to load module");
@ -73,9 +77,6 @@ pub fn helper<'a>(
), ),
}; };
let target = target_lexicon::Triple::host();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let mut lines = Vec::new(); let mut lines = Vec::new();
// errors whose reporting we delay (so we can see that code gen generates runtime errors) // errors whose reporting we delay (so we can see that code gen generates runtime errors)
let mut delayed_errors = Vec::new(); let mut delayed_errors = Vec::new();
@ -294,38 +295,6 @@ pub fn helper<'a>(
(main_fn_name, delayed_errors.join("\n"), lib) (main_fn_name, delayed_errors.join("\n"), lib)
} }
// TODO this is almost all code duplication with assert_llvm_evals_to
// the only difference is that this calls uniq_expr instead of can_expr.
// Should extract the common logic into test helpers.
#[macro_export]
macro_rules! assert_opt_evals_to {
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {
use bumpalo::Bump;
use inkwell::context::Context;
use roc_gen::run_jit_function;
let arena = Bump::new();
let context = Context::create();
let stdlib = roc_builtins::unique::uniq_stdlib();
let (main_fn_name, errors, lib) =
$crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context);
let transform = |success| {
let expected = $expected;
let given = $transform(success);
assert_eq!(&given, &expected);
};
run_jit_function!(lib, main_fn_name, $ty, transform, errors)
};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
assert_opt_evals_to!($src, $expected, $ty, $transform, true)
};
}
#[macro_export] #[macro_export]
macro_rules! assert_llvm_evals_to { macro_rules! assert_llvm_evals_to {
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {
@ -334,9 +303,10 @@ macro_rules! assert_llvm_evals_to {
use roc_gen::run_jit_function; use roc_gen::run_jit_function;
let arena = Bump::new(); let arena = Bump::new();
let context = Context::create(); let context = Context::create();
let stdlib = roc_builtins::std::standard_stdlib();
// NOTE the stdlib must be in the arena; just taking a reference will segfault
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
let (main_fn_name, errors, lib) = let (main_fn_name, errors, lib) =
$crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context); $crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context);
@ -374,7 +344,8 @@ macro_rules! assert_evals_to {
assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak); assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak);
} }
{ {
assert_opt_evals_to!($src, $expected, $ty, $transform, $leak); // NOTE at the moment, the optimized tests do the same thing
// assert_opt_evals_to!($src, $expected, $ty, $transform, $leak);
} }
}; };
} }

View file

@ -307,6 +307,16 @@ impl Assembler<AArch64GPReg> for AArch64Assembler {
} }
} }
#[inline(always)]
fn sub_reg64_reg64_reg64(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GPReg,
_src1: AArch64GPReg,
_src2: AArch64GPReg,
) {
unimplemented!("registers subtractions not implemented yet for AArch64");
}
#[inline(always)] #[inline(always)]
fn ret(buf: &mut Vec<'_, u8>) { fn ret(buf: &mut Vec<'_, u8>) {
ret_reg64(buf, AArch64GPReg::LR) ret_reg64(buf, AArch64GPReg::LR)

View file

@ -51,6 +51,7 @@ pub trait Assembler<GPReg: GPRegTrait> {
fn mov_reg64_stack32(buf: &mut Vec<'_, u8>, dst: GPReg, offset: i32); fn mov_reg64_stack32(buf: &mut Vec<'_, u8>, dst: GPReg, offset: i32);
fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GPReg); fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GPReg);
fn sub_reg64_reg64_imm32(buf: &mut Vec<'_, u8>, dst: GPReg, src1: GPReg, imm32: i32); fn sub_reg64_reg64_imm32(buf: &mut Vec<'_, u8>, dst: GPReg, src1: GPReg, imm32: i32);
fn sub_reg64_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GPReg, src1: GPReg, src2: GPReg);
fn ret(buf: &mut Vec<'_, u8>); fn ret(buf: &mut Vec<'_, u8>);
} }
@ -194,6 +195,19 @@ impl<'a, GPReg: GPRegTrait, ASM: Assembler<GPReg>, CC: CallConv<GPReg>> Backend<
Ok(()) Ok(())
} }
fn build_num_sub_i64(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
) -> Result<(), String> {
let dst_reg = self.claim_gp_reg(dst)?;
let src1_reg = self.load_to_reg(src1)?;
let src2_reg = self.load_to_reg(src2)?;
ASM::sub_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
Ok(())
}
fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String> { fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String> {
match lit { match lit {
Literal::Int(x) => { Literal::Int(x) => {

View file

@ -310,6 +310,20 @@ impl Assembler<X86_64GPReg> for X86_64Assembler {
} }
} }
#[inline(always)] #[inline(always)]
fn sub_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: X86_64GPReg,
src1: X86_64GPReg,
src2: X86_64GPReg,
) {
if dst == src1 {
sub_reg64_reg64(buf, dst, src2);
} else {
mov_reg64_reg64(buf, dst, src1);
sub_reg64_reg64(buf, dst, src2);
}
}
#[inline(always)]
fn ret(buf: &mut Vec<'_, u8>) { fn ret(buf: &mut Vec<'_, u8>) {
ret(buf); ret(buf);
} }
@ -379,6 +393,16 @@ fn add_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GPReg, src: X86_64GPReg) {
buf.extend(&[rex, 0x01, 0xC0 + dst_mod + src_mod]); buf.extend(&[rex, 0x01, 0xC0 + dst_mod + src_mod]);
} }
/// `SUB r/m64,r64` -> Sub r64 to r/m64.
#[inline(always)]
fn sub_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GPReg, src: X86_64GPReg) {
let rex = add_rm_extension(dst, REX_W);
let rex = add_reg_extension(src, rex);
let dst_mod = dst as u8 % 8;
let src_mod = (src as u8 % 8) << 3;
buf.extend(&[rex, 0x29, 0xC0 + dst_mod + src_mod]);
}
/// `CMOVL r64,r/m64` -> Move if less (SF≠ OF). /// `CMOVL r64,r/m64` -> Move if less (SF≠ OF).
#[inline(always)] #[inline(always)]
fn cmovl_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GPReg, src: X86_64GPReg) { fn cmovl_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GPReg, src: X86_64GPReg) {

View file

@ -24,7 +24,7 @@ pub struct Env<'a> {
} }
// INLINED_SYMBOLS is a set of all of the functions we automatically inline if seen. // INLINED_SYMBOLS is a set of all of the functions we automatically inline if seen.
const INLINED_SYMBOLS: [Symbol; 2] = [Symbol::NUM_ABS, Symbol::NUM_ADD]; const INLINED_SYMBOLS: [Symbol; 3] = [Symbol::NUM_ABS, Symbol::NUM_ADD, Symbol::NUM_SUB];
// These relocations likely will need a length. // These relocations likely will need a length.
// They may even need more definition, but this should be at least good enough for how we will use elf. // They may even need more definition, but this should be at least good enough for how we will use elf.
@ -82,6 +82,18 @@ where
self.free_symbols(stmt); self.free_symbols(stmt);
Ok(()) Ok(())
} }
Stmt::Invoke {
symbol,
layout,
call,
pass,
fail: _,
} => {
// for now, treat invoke as a normal call
let stmt = Stmt::Let(*symbol, Expr::Call(call.clone()), layout.clone(), pass);
self.build_stmt(&stmt)
}
x => Err(format!("the statement, {:?}, is not yet implemented", x)), x => Err(format!("the statement, {:?}, is not yet implemented", x)),
} }
} }
@ -103,25 +115,34 @@ where
} }
Ok(()) Ok(())
} }
Expr::FunctionCall { Expr::Call(roc_mono::ir::Call {
call_type: CallType::ByName(func_sym), call_type,
args, arguments,
.. }) => {
} => { match call_type {
CallType::ByName { name: func_sym, .. } => {
match *func_sym { match *func_sym {
Symbol::NUM_ABS => { Symbol::NUM_ABS => {
// Instead of calling the function, just inline it. // Instead of calling the function, just inline it.
self.build_expr(sym, &Expr::RunLowLevel(LowLevel::NumAbs, args), layout) self.build_run_low_level(sym, &LowLevel::NumAbs, arguments, layout)
} }
Symbol::NUM_ADD => { Symbol::NUM_ADD => {
// Instead of calling the function, just inline it. // Instead of calling the function, just inline it.
self.build_expr(sym, &Expr::RunLowLevel(LowLevel::NumAdd, args), layout) self.build_run_low_level(sym, &LowLevel::NumAdd, arguments, layout)
}
Symbol::NUM_SUB => {
// Instead of calling the function, just inline it.
self.build_run_low_level(sym, &LowLevel::NumSub, arguments, layout)
} }
x => Err(format!("the function, {:?}, is not yet implemented", x)), x => Err(format!("the function, {:?}, is not yet implemented", x)),
} }
} }
Expr::RunLowLevel(lowlevel, args) => {
self.build_run_low_level(sym, lowlevel, args, layout) CallType::LowLevel { op: lowlevel } => {
self.build_run_low_level(sym, lowlevel, arguments, layout)
}
x => Err(format!("the call type, {:?}, is not yet implemented", x)),
}
} }
x => Err(format!("the expression, {:?}, is not yet implemented", x)), x => Err(format!("the expression, {:?}, is not yet implemented", x)),
} }
@ -155,6 +176,15 @@ where
x => Err(format!("layout, {:?}, not implemented yet", x)), x => Err(format!("layout, {:?}, not implemented yet", x)),
} }
} }
LowLevel::NumSub => {
// TODO: when this is expanded to floats. deal with typecasting here, and then call correct low level method.
match layout {
Layout::Builtin(Builtin::Int64) => {
self.build_num_sub_i64(sym, &args[0], &args[1])
}
x => Err(format!("layout, {:?}, not implemented yet", x)),
}
}
x => Err(format!("low level, {:?}. is not yet implemented", x)), x => Err(format!("low level, {:?}. is not yet implemented", x)),
} }
} }
@ -163,7 +193,7 @@ where
/// It only deals with inputs and outputs of i64 type. /// It only deals with inputs and outputs of i64 type.
fn build_num_abs_i64(&mut self, dst: &Symbol, src: &Symbol) -> Result<(), String>; fn build_num_abs_i64(&mut self, dst: &Symbol, src: &Symbol) -> Result<(), String>;
/// build_num_add_i64 stores the absolute value of src into dst. /// build_num_add_i64 stores the sum of src1 and src2 into dst.
/// It only deals with inputs and outputs of i64 type. /// It only deals with inputs and outputs of i64 type.
fn build_num_add_i64( fn build_num_add_i64(
&mut self, &mut self,
@ -172,6 +202,15 @@ where
src2: &Symbol, src2: &Symbol,
) -> Result<(), String>; ) -> Result<(), String>;
/// build_num_sub_i64 stores the `src1 - src2` difference into dst.
/// It only deals with inputs and outputs of i64 type.
fn build_num_sub_i64(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
) -> Result<(), String>;
/// literal_map gets the map from symbol to literal, used for lazy loading and literal folding. /// literal_map gets the map from symbol to literal, used for lazy loading and literal folding.
fn literal_map(&mut self) -> &mut MutMap<Symbol, Literal<'a>>; fn literal_map(&mut self) -> &mut MutMap<Symbol, Literal<'a>>;
@ -244,36 +283,9 @@ where
match expr { match expr {
Expr::Literal(_) => {} Expr::Literal(_) => {}
Expr::FunctionPointer(sym, _) => self.set_last_seen(*sym, stmt), Expr::FunctionPointer(sym, _) => self.set_last_seen(*sym, stmt),
Expr::FunctionCall {
call_type, args, .. Expr::Call(call) => self.scan_ast_call(call, stmt),
} => {
for sym in *args {
self.set_last_seen(*sym, stmt);
}
match call_type {
CallType::ByName(sym) => {
// For functions that we won't inline, we should not be a leaf function.
if !INLINED_SYMBOLS.contains(sym) {
self.set_not_leaf_function();
}
}
CallType::ByPointer(sym) => {
self.set_not_leaf_function();
self.set_last_seen(*sym, stmt);
}
}
}
Expr::RunLowLevel(_, args) => {
for sym in *args {
self.set_last_seen(*sym, stmt);
}
}
Expr::ForeignCall { arguments, .. } => {
for sym in *arguments {
self.set_last_seen(*sym, stmt);
}
self.set_not_leaf_function();
}
Expr::Tag { arguments, .. } => { Expr::Tag { arguments, .. } => {
for sym in *arguments { for sym in *arguments {
self.set_last_seen(*sym, stmt); self.set_last_seen(*sym, stmt);
@ -320,6 +332,20 @@ where
} }
self.scan_ast(following); self.scan_ast(following);
} }
Stmt::Invoke {
symbol,
layout,
call,
pass,
fail: _,
} => {
// for now, treat invoke as a normal call
let stmt = Stmt::Let(*symbol, Expr::Call(call.clone()), layout.clone(), pass);
self.scan_ast(&stmt);
}
Stmt::Switch { Stmt::Switch {
cond_symbol, cond_symbol,
branches, branches,
@ -335,6 +361,7 @@ where
Stmt::Ret(sym) => { Stmt::Ret(sym) => {
self.set_last_seen(*sym, stmt); self.set_last_seen(*sym, stmt);
} }
Stmt::Rethrow => {}
Stmt::Inc(sym, following) => { Stmt::Inc(sym, following) => {
self.set_last_seen(*sym, stmt); self.set_last_seen(*sym, stmt);
self.scan_ast(following); self.scan_ast(following);
@ -364,4 +391,30 @@ where
Stmt::RuntimeError(_) => {} Stmt::RuntimeError(_) => {}
} }
} }
fn scan_ast_call(&mut self, call: &roc_mono::ir::Call, stmt: &roc_mono::ir::Stmt<'a>) {
let roc_mono::ir::Call {
call_type,
arguments,
} = call;
for sym in *arguments {
self.set_last_seen(*sym, stmt);
}
match call_type {
CallType::ByName { name: sym, .. } => {
// For functions that we won't inline, we should not be a leaf function.
if !INLINED_SYMBOLS.contains(sym) {
self.set_not_leaf_function();
}
}
CallType::ByPointer { name: sym, .. } => {
self.set_not_leaf_function();
self.set_last_seen(*sym, stmt);
}
CallType::LowLevel { .. } => {}
CallType::Foreign { .. } => self.set_not_leaf_function(),
}
}
} }

View file

@ -40,6 +40,19 @@ mod gen_num {
); );
} }
#[test]
fn gen_sub_i64() {
assert_evals_to!(
indoc!(
r#"
1 - 2 - 3
"#
),
-4,
i64
);
}
#[test] #[test]
fn i64_force_stack() { fn i64_force_stack() {
// This claims 33 registers. One more than Arm and RISC-V, and many more than x86-64. // This claims 33 registers. One more than Arm and RISC-V, and many more than x86-64.

View file

@ -47,9 +47,10 @@ pub fn helper<'a>(
arena, arena,
filename, filename,
&module_src, &module_src,
stdlib, &stdlib,
src_dir, src_dir,
exposed_types, exposed_types,
8,
); );
let mut loaded = loaded.expect("failed to load module"); let mut loaded = loaded.expect("failed to load module");

View file

@ -699,7 +699,7 @@ struct State<'a> {
pub root_id: ModuleId, pub root_id: ModuleId,
pub platform_id: Option<ModuleId>, pub platform_id: Option<ModuleId>,
pub goal_phase: Phase, pub goal_phase: Phase,
pub stdlib: StdLib, pub stdlib: &'a StdLib,
pub exposed_types: SubsByModule, pub exposed_types: SubsByModule,
pub output_path: Option<&'a str>, pub output_path: Option<&'a str>,
pub platform_path: Option<To<'a>>, pub platform_path: Option<To<'a>>,
@ -944,9 +944,10 @@ fn enqueue_task<'a>(
pub fn load_and_typecheck( pub fn load_and_typecheck(
arena: &Bump, arena: &Bump,
filename: PathBuf, filename: PathBuf,
stdlib: StdLib, stdlib: &StdLib,
src_dir: &Path, src_dir: &Path,
exposed_types: SubsByModule, exposed_types: SubsByModule,
ptr_bytes: u32,
) -> Result<LoadedModule, LoadingProblem> { ) -> Result<LoadedModule, LoadingProblem> {
use LoadResult::*; use LoadResult::*;
@ -959,6 +960,7 @@ pub fn load_and_typecheck(
src_dir, src_dir,
exposed_types, exposed_types,
Phase::SolveTypes, Phase::SolveTypes,
ptr_bytes,
)? { )? {
Monomorphized(_) => unreachable!(""), Monomorphized(_) => unreachable!(""),
TypeChecked(module) => Ok(module), TypeChecked(module) => Ok(module),
@ -968,9 +970,10 @@ pub fn load_and_typecheck(
pub fn load_and_monomorphize<'a>( pub fn load_and_monomorphize<'a>(
arena: &'a Bump, arena: &'a Bump,
filename: PathBuf, filename: PathBuf,
stdlib: StdLib, stdlib: &'a StdLib,
src_dir: &Path, src_dir: &Path,
exposed_types: SubsByModule, exposed_types: SubsByModule,
ptr_bytes: u32,
) -> Result<MonomorphizedModule<'a>, LoadingProblem> { ) -> Result<MonomorphizedModule<'a>, LoadingProblem> {
use LoadResult::*; use LoadResult::*;
@ -983,6 +986,7 @@ pub fn load_and_monomorphize<'a>(
src_dir, src_dir,
exposed_types, exposed_types,
Phase::MakeSpecializations, Phase::MakeSpecializations,
ptr_bytes,
)? { )? {
Monomorphized(module) => Ok(module), Monomorphized(module) => Ok(module),
TypeChecked(_) => unreachable!(""), TypeChecked(_) => unreachable!(""),
@ -993,9 +997,10 @@ pub fn load_and_monomorphize_from_str<'a>(
arena: &'a Bump, arena: &'a Bump,
filename: PathBuf, filename: PathBuf,
src: &'a str, src: &'a str,
stdlib: StdLib, stdlib: &'a StdLib,
src_dir: &Path, src_dir: &Path,
exposed_types: SubsByModule, exposed_types: SubsByModule,
ptr_bytes: u32,
) -> Result<MonomorphizedModule<'a>, LoadingProblem> { ) -> Result<MonomorphizedModule<'a>, LoadingProblem> {
use LoadResult::*; use LoadResult::*;
@ -1008,6 +1013,7 @@ pub fn load_and_monomorphize_from_str<'a>(
src_dir, src_dir,
exposed_types, exposed_types,
Phase::MakeSpecializations, Phase::MakeSpecializations,
ptr_bytes,
)? { )? {
Monomorphized(module) => Ok(module), Monomorphized(module) => Ok(module),
TypeChecked(_) => unreachable!(""), TypeChecked(_) => unreachable!(""),
@ -1140,10 +1146,11 @@ fn load<'a>(
arena: &'a Bump, arena: &'a Bump,
//filename: PathBuf, //filename: PathBuf,
load_start: LoadStart<'a>, load_start: LoadStart<'a>,
stdlib: StdLib, stdlib: &'a StdLib,
src_dir: &Path, src_dir: &Path,
exposed_types: SubsByModule, exposed_types: SubsByModule,
goal_phase: Phase, goal_phase: Phase,
ptr_bytes: u32,
) -> Result<LoadResult<'a>, LoadingProblem> ) -> Result<LoadResult<'a>, LoadingProblem>
where where
{ {
@ -1259,7 +1266,13 @@ where
// added. In that case, do nothing, and keep waiting // added. In that case, do nothing, and keep waiting
// until we receive a Shutdown message. // until we receive a Shutdown message.
if let Some(task) = find_task(&worker, injector, stealers) { if let Some(task) = find_task(&worker, injector, stealers) {
run_task(task, worker_arena, src_dir, msg_tx.clone()) run_task(
task,
worker_arena,
src_dir,
msg_tx.clone(),
ptr_bytes,
)
.expect("Msg channel closed unexpectedly."); .expect("Msg channel closed unexpectedly.");
} }
} }
@ -1790,8 +1803,6 @@ fn update<'a>(
if state.dependencies.solved_all() && state.goal_phase == Phase::MakeSpecializations { if state.dependencies.solved_all() && state.goal_phase == Phase::MakeSpecializations {
debug_assert!(work.is_empty(), "still work remaining {:?}", &work); debug_assert!(work.is_empty(), "still work remaining {:?}", &work);
Proc::insert_refcount_operations(arena, &mut state.procedures);
// display the mono IR of the module, for debug purposes // display the mono IR of the module, for debug purposes
if roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS { if roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS {
let procs_string = state let procs_string = state
@ -1805,6 +1816,8 @@ fn update<'a>(
println!("{}", result); println!("{}", result);
} }
Proc::insert_refcount_operations(arena, &mut state.procedures);
msg_tx msg_tx
.send(Msg::FinishedAllSpecialization { .send(Msg::FinishedAllSpecialization {
subs, subs,
@ -3341,6 +3354,7 @@ fn make_specializations<'a>(
mut layout_cache: LayoutCache<'a>, mut layout_cache: LayoutCache<'a>,
specializations_we_must_make: ExternalSpecializations, specializations_we_must_make: ExternalSpecializations,
mut module_timing: ModuleTiming, mut module_timing: ModuleTiming,
ptr_bytes: u32,
) -> Msg<'a> { ) -> Msg<'a> {
let make_specializations_start = SystemTime::now(); let make_specializations_start = SystemTime::now();
let mut mono_problems = Vec::new(); let mut mono_problems = Vec::new();
@ -3351,6 +3365,7 @@ fn make_specializations<'a>(
subs: &mut subs, subs: &mut subs,
home, home,
ident_ids: &mut ident_ids, ident_ids: &mut ident_ids,
ptr_bytes,
}; };
procs procs
@ -3396,6 +3411,7 @@ fn build_pending_specializations<'a>(
decls: Vec<Declaration>, decls: Vec<Declaration>,
mut module_timing: ModuleTiming, mut module_timing: ModuleTiming,
mut layout_cache: LayoutCache<'a>, mut layout_cache: LayoutCache<'a>,
ptr_bytes: u32,
// TODO remove // TODO remove
exposed_to_host: MutMap<Symbol, Variable>, exposed_to_host: MutMap<Symbol, Variable>,
) -> Msg<'a> { ) -> Msg<'a> {
@ -3410,6 +3426,7 @@ fn build_pending_specializations<'a>(
subs: &mut subs, subs: &mut subs,
home, home,
ident_ids: &mut ident_ids, ident_ids: &mut ident_ids,
ptr_bytes,
}; };
// Add modules' decls to Procs // Add modules' decls to Procs
@ -3613,6 +3630,7 @@ fn run_task<'a>(
arena: &'a Bump, arena: &'a Bump,
src_dir: &Path, src_dir: &Path,
msg_tx: MsgSender<'a>, msg_tx: MsgSender<'a>,
ptr_bytes: u32,
) -> Result<(), LoadingProblem> { ) -> Result<(), LoadingProblem> {
use BuildTask::*; use BuildTask::*;
@ -3685,6 +3703,7 @@ fn run_task<'a>(
decls, decls,
module_timing, module_timing,
layout_cache, layout_cache,
ptr_bytes,
exposed_to_host, exposed_to_host,
)), )),
MakeSpecializations { MakeSpecializations {
@ -3704,6 +3723,7 @@ fn run_task<'a>(
layout_cache, layout_cache,
specializations_we_must_make, specializations_we_must_make,
module_timing, module_timing,
ptr_bytes,
)), )),
}?; }?;

View file

@ -1,6 +1,6 @@
app "quicksort" provides [ swap, partition, partitionHelp, quicksort ] to "./platform" app "quicksort" provides [ swap, partition, partitionHelp, quicksort ] to "./platform"
quicksort : List (Num a), I64, I64 -> List (Num a) quicksort : List (Num a), Nat, Nat -> List (Num a)
quicksort = \list, low, high -> quicksort = \list, low, high ->
when partition low high list is when partition low high list is
Pair partitionIndex partitioned -> Pair partitionIndex partitioned ->
@ -9,7 +9,7 @@ quicksort = \list, low, high ->
|> quicksort (partitionIndex + 1) high |> quicksort (partitionIndex + 1) high
swap : I64, I64, List a -> List a swap : Nat, Nat, List a -> List a
swap = \i, j, list -> swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) -> Pair (Ok atI) (Ok atJ) ->
@ -21,7 +21,7 @@ swap = \i, j, list ->
[] []
partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ] partition : Nat, Nat, List (Num a) -> [ Pair Nat (List (Num a)) ]
partition = \low, high, initialList -> partition = \low, high, initialList ->
when List.get initialList high is when List.get initialList high is
Ok pivot -> Ok pivot ->
@ -33,7 +33,7 @@ partition = \low, high, initialList ->
Pair (low - 1) initialList Pair (low - 1) initialList
partitionHelp : I64, I64, List (Num a), I64, (Num a) -> [ Pair I64 (List (Num a)) ] partitionHelp : Nat, Nat, List (Num a), Nat, (Num a) -> [ Pair Nat (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot -> partitionHelp = \i, j, list, high, pivot ->
if j < high then if j < high then
when List.get list j is when List.get list j is

View file

@ -1,7 +1,7 @@
app "quicksort" provides [ quicksort ] to "./platform" app "quicksort" provides [ quicksort ] to "./platform"
quicksort = \originalList -> quicksort = \originalList ->
quicksortHelp : List (Num a), I64, I64 -> List (Num a) quicksortHelp : List (Num a), Nat, Nat -> List (Num a)
quicksortHelp = \list, low, high -> quicksortHelp = \list, low, high ->
if low < high then if low < high then
when partition low high list is when partition low high list is
@ -13,7 +13,7 @@ quicksort = \originalList ->
list list
swap : I64, I64, List a -> List a swap : Nat, Nat, List a -> List a
swap = \i, j, list -> swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) -> Pair (Ok atI) (Ok atJ) ->
@ -24,7 +24,7 @@ quicksort = \originalList ->
_ -> _ ->
[] []
partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ] partition : Nat, Nat, List (Num a) -> [ Pair Nat (List (Num a)) ]
partition = \low, high, initialList -> partition = \low, high, initialList ->
when List.get initialList high is when List.get initialList high is
Ok pivot -> Ok pivot ->
@ -36,7 +36,7 @@ quicksort = \originalList ->
Pair (low - 1) initialList Pair (low - 1) initialList
partitionHelp : I64, I64, List (Num a), I64, (Num a) -> [ Pair I64 (List (Num a)) ] partitionHelp : Nat, Nat, List (Num a), Nat, (Num a) -> [ Pair Nat (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot -> partitionHelp = \i, j, list, high, pivot ->
if j < high then if j < high then
when List.get list j is when List.get list j is

View file

@ -2,7 +2,7 @@ interface Quicksort
exposes [ swap, partition, quicksort ] exposes [ swap, partition, quicksort ]
imports [] imports []
quicksort : List (Num a), I64, I64 -> List (Num a) quicksort : List (Num a), Nat, Nat -> List (Num a)
quicksort = \list, low, high -> quicksort = \list, low, high ->
when partition low high list is when partition low high list is
Pair partitionIndex partitioned -> Pair partitionIndex partitioned ->
@ -11,7 +11,7 @@ quicksort = \list, low, high ->
|> quicksort (partitionIndex + 1) high |> quicksort (partitionIndex + 1) high
swap : I64, I64, List a -> List a swap : Nat, Nat, List a -> List a
swap = \i, j, list -> swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) -> Pair (Ok atI) (Ok atJ) ->
@ -23,7 +23,7 @@ swap = \i, j, list ->
[] []
partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ] partition : Nat, Nat, List (Num a) -> [ Pair Nat (List (Num a)) ]
partition = \low, high, initialList -> partition = \low, high, initialList ->
when List.get initialList high is when List.get initialList high is
Ok pivot -> Ok pivot ->
@ -35,7 +35,7 @@ partition = \low, high, initialList ->
Pair (low - 1) initialList Pair (low - 1) initialList
partitionHelp : I64, I64, List (Num a), I64, (Num a) -> [ Pair I64 (List (Num a)) ] partitionHelp : Nat, Nat, List (Num a), Nat, (Num a) -> [ Pair Nat (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot -> partitionHelp = \i, j, list, high, pivot ->
if j < high then if j < high then
when List.get list j is when List.get list j is

View file

@ -82,9 +82,10 @@ mod test_load {
roc_load::file::load_and_typecheck( roc_load::file::load_and_typecheck(
arena, arena,
full_file_path, full_file_path,
stdlib, &stdlib,
dir.path(), dir.path(),
exposed_types, exposed_types,
8,
) )
}; };
@ -123,9 +124,10 @@ mod test_load {
let loaded = roc_load::file::load_and_typecheck( let loaded = roc_load::file::load_and_typecheck(
&arena, &arena,
filename, filename,
roc_builtins::std::standard_stdlib(), &roc_builtins::std::standard_stdlib(),
src_dir.as_path(), src_dir.as_path(),
subs_by_module, subs_by_module,
8,
); );
let mut loaded_module = loaded.expect("Test module failed to load"); let mut loaded_module = loaded.expect("Test module failed to load");
@ -285,9 +287,10 @@ mod test_load {
let loaded = roc_load::file::load_and_typecheck( let loaded = roc_load::file::load_and_typecheck(
&arena, &arena,
filename, filename,
roc_builtins::std::standard_stdlib(), &roc_builtins::std::standard_stdlib(),
src_dir.as_path(), src_dir.as_path(),
subs_by_module, subs_by_module,
8,
); );
let mut loaded_module = loaded.expect("Test module failed to load"); let mut loaded_module = loaded.expect("Test module failed to load");
@ -357,14 +360,14 @@ mod test_load {
expect_types( expect_types(
loaded_module, loaded_module,
hashmap! { hashmap! {
"floatTest" => "F64", "floatTest" => "Float *",
"divisionFn" => "F64, F64 -> Result F64 [ DivByZero ]*", "divisionFn" => "Float a, Float a -> Result (Float a) [ DivByZero ]*",
"divisionTest" => "Result F64 [ DivByZero ]*", "divisionTest" => "Result (Float *) [ DivByZero ]*",
"intTest" => "I64", "intTest" => "Int *",
"x" => "F64", "x" => "Float *",
"constantNum" => "Num *", "constantNum" => "Num *",
"divDep1ByDep2" => "Result F64 [ DivByZero ]*", "divDep1ByDep2" => "Result (Float *) [ DivByZero ]*",
"fromDep2" => "F64", "fromDep2" => "Float *",
}, },
); );
} }
@ -377,10 +380,10 @@ mod test_load {
expect_types( expect_types(
loaded_module, loaded_module,
hashmap! { hashmap! {
"swap" => "I64, I64, List a -> List a", "swap" => "Nat, Nat, List a -> List a",
"partition" => "I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ]", "partition" => "Nat, Nat, List (Num a) -> [ Pair Nat (List (Num a)) ]",
"partitionHelp" => "I64, I64, List (Num a), I64, Num a -> [ Pair I64 (List (Num a)) ]", "partitionHelp" => "Nat, Nat, List (Num a), Nat, Num a -> [ Pair Nat (List (Num a)) ]",
"quicksort" => "List (Num a), I64, I64 -> List (Num a)", "quicksort" => "List (Num a), Nat, Nat -> List (Num a)",
}, },
); );
} }
@ -406,10 +409,10 @@ mod test_load {
expect_types( expect_types(
loaded_module, loaded_module,
hashmap! { hashmap! {
"swap" => "I64, I64, List a -> List a", "swap" => "Nat, Nat, List a -> List a",
"partition" => "I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ]", "partition" => "Nat, Nat, List (Num a) -> [ Pair Nat (List (Num a)) ]",
"partitionHelp" => "I64, I64, List (Num a), I64, Num a -> [ Pair I64 (List (Num a)) ]", "partitionHelp" => "Nat, Nat, List (Num a), Nat, Num a -> [ Pair Nat (List (Num a)) ]",
"quicksort" => "List (Num a), I64, I64 -> List (Num a)", "quicksort" => "List (Num a), Nat, Nat -> List (Num a)",
}, },
); );
} }
@ -454,7 +457,7 @@ mod test_load {
expect_types( expect_types(
loaded_module, loaded_module,
hashmap! { hashmap! {
"blah2" => "F64", "blah2" => "Float *",
"blah3" => "Str", "blah3" => "Str",
"str" => "Str", "str" => "Str",
"alwaysThree" => "* -> Str", "alwaysThree" => "* -> Str",
@ -476,7 +479,7 @@ mod test_load {
expect_types( expect_types(
loaded_module, loaded_module,
hashmap! { hashmap! {
"blah2" => "F64", "blah2" => "Float *",
"blah3" => "Str", "blah3" => "Str",
"str" => "Str", "str" => "Str",
"alwaysThree" => "* -> Str", "alwaysThree" => "* -> Str",

View file

@ -1,423 +0,0 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate maplit;
extern crate bumpalo;
extern crate inlinable_string;
extern crate roc_collections;
extern crate roc_load;
extern crate roc_module;
mod helpers;
#[cfg(test)]
mod test_uniq_load {
use crate::helpers::fixtures_dir;
use bumpalo::Bump;
use inlinable_string::InlinableString;
use roc_builtins::unique;
use roc_can::def::Declaration::*;
use roc_can::def::Def;
use roc_collections::all::MutMap;
use roc_constrain::module::SubsByModule;
use roc_load::file::LoadedModule;
use roc_module::ident::ModuleName;
use roc_module::symbol::{Interns, ModuleId};
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
use roc_types::subs::Subs;
use std::collections::HashMap;
// HELPERS
fn load_fixture(
dir_name: &str,
module_name: &str,
subs_by_module: SubsByModule,
) -> LoadedModule {
let arena = Bump::new();
let src_dir = fixtures_dir().join(dir_name);
let filename = src_dir.join(format!("{}.roc", module_name));
let loaded = roc_load::file::load_and_typecheck(
&arena,
filename,
unique::uniq_stdlib(),
src_dir.as_path(),
subs_by_module,
);
let mut loaded_module = loaded.expect("Test module failed to load");
let home = loaded_module.module_id;
assert_eq!(
loaded_module.can_problems.remove(&home).unwrap_or_default(),
Vec::new()
);
assert_eq!(
loaded_module
.type_problems
.remove(&home)
.unwrap_or_default(),
Vec::new()
);
let expected_name = loaded_module
.interns
.module_ids
.get_name(loaded_module.module_id)
.expect("Test ModuleID not found in module_ids");
// App module names are hardcoded and not based on anything user-specified
if expected_name != ModuleName::APP {
assert_eq!(expected_name, &InlinableString::from(module_name));
}
loaded_module
}
fn expect_def(
interns: &Interns,
subs: &mut Subs,
home: ModuleId,
def: &Def,
expected_types: &mut HashMap<&str, &str>,
) {
for (symbol, expr_var) in &def.pattern_vars {
let content = subs.get(*expr_var).content;
name_all_type_vars(*expr_var, subs);
let actual_str = content_to_string(content, subs, home, &interns);
let fully_qualified = symbol.fully_qualified(&interns, home).to_string();
let expected_type = expected_types
.remove(fully_qualified.as_str())
.unwrap_or_else(|| {
panic!("Defs included an unexpected symbol: {:?}", fully_qualified)
});
assert_eq!((&symbol, expected_type), (&symbol, actual_str.as_str()));
}
}
fn expect_types(mut loaded_module: LoadedModule, mut expected_types: HashMap<&str, &str>) {
let home = loaded_module.module_id;
let mut subs = loaded_module.solved.into_inner();
assert_eq!(
loaded_module.can_problems.remove(&home).unwrap_or_default(),
Vec::new()
);
assert_eq!(
loaded_module
.type_problems
.remove(&home)
.unwrap_or_default(),
Vec::new()
);
for decl in loaded_module.declarations_by_id.remove(&home).unwrap() {
match decl {
Declare(def) => expect_def(
&loaded_module.interns,
&mut subs,
home,
&def,
&mut expected_types,
),
DeclareRec(defs) => {
for def in defs {
expect_def(
&loaded_module.interns,
&mut subs,
home,
&def,
&mut expected_types,
);
}
}
Builtin(_) => {}
cycle @ InvalidCycle(_, _) => {
panic!("Unexpected cyclic def in module declarations: {:?}", cycle);
}
};
}
assert_eq!(
expected_types,
HashMap::default(),
"Some expected types were not found in the defs"
);
}
// TESTS
#[test]
fn interface_with_deps() {
let arena = Bump::new();
let subs_by_module = MutMap::default();
let src_dir = fixtures_dir().join("interface_with_deps");
let filename = src_dir.join("Primary.roc");
let loaded = roc_load::file::load_and_typecheck(
&arena,
filename,
roc_builtins::std::standard_stdlib(),
src_dir.as_path(),
subs_by_module,
);
let mut loaded_module = loaded.expect("Test module failed to load");
let home = loaded_module.module_id;
assert_eq!(
loaded_module.can_problems.remove(&home).unwrap_or_default(),
Vec::new()
);
assert_eq!(
loaded_module
.type_problems
.remove(&home)
.unwrap_or_default(),
Vec::new()
);
let def_count: usize = loaded_module
.declarations_by_id
.remove(&loaded_module.module_id)
.unwrap()
.into_iter()
.map(|decl| decl.def_count())
.sum();
let expected_name = loaded_module
.interns
.module_ids
.get_name(loaded_module.module_id)
.expect("Test ModuleID not found in module_ids");
assert_eq!(expected_name, &InlinableString::from("Primary"));
assert_eq!(def_count, 10);
}
#[test]
fn load_unit() {
let subs_by_module = MutMap::default();
let loaded_module = load_fixture("no_deps", "Unit", subs_by_module);
expect_types(
loaded_module,
hashmap! {
"unit" => "Attr * Unit",
},
);
}
#[test]
fn import_alias() {
let subs_by_module = MutMap::default();
let loaded_module = load_fixture("interface_with_deps", "ImportAlias", subs_by_module);
expect_types(
loaded_module,
hashmap! {
"unit" => "Attr * Dep1.Unit",
},
);
}
#[test]
fn load_and_typecheck() {
let subs_by_module = MutMap::default();
let loaded_module = load_fixture("interface_with_deps", "WithBuiltins", subs_by_module);
expect_types(
loaded_module,
hashmap! {
"floatTest" => "Attr Shared F64",
"divisionFn" => "Attr Shared (Attr * F64, Attr * F64 -> Attr * (Result (Attr * F64) (Attr * [ DivByZero ]*)))",
"divisionTest" => "Attr * (Result (Attr * F64) (Attr * [ DivByZero ]*))",
"intTest" => "Attr * I64",
"x" => "Attr * F64",
"constantNum" => "Attr * (Num (Attr * *))",
"divDep1ByDep2" => "Attr * (Result (Attr * F64) (Attr * [ DivByZero ]*))",
"fromDep2" => "Attr * F64",
},
);
}
#[test]
#[ignore]
fn load_astar() {
let subs_by_module = MutMap::default();
let loaded_module = load_fixture("interface_with_deps", "AStar", subs_by_module);
expect_types(
loaded_module,
hashmap! {
"findPath" => "Attr * (Attr * { costFunction : Attr Shared (Attr Shared position, Attr Shared position -> Attr * F64), end : Attr Shared position, moveFunction : Attr Shared (Attr Shared position -> Attr * (Set (Attr * position))), start : Attr Shared position } -> Attr * (Result (Attr * (List (Attr Shared position))) (Attr * [ KeyNotFound ]*)))",
"initialModel" => "Attr * (Attr Shared position -> Attr * (Model (Attr Shared position)))",
"reconstructPath" => "Attr Shared (Attr Shared (Map (Attr * position) (Attr Shared position)), Attr Shared position -> Attr * (List (Attr Shared position)))",
"updateCost" => "Attr * (Attr Shared position, Attr Shared position, Attr Shared (Model (Attr Shared position)) -> Attr Shared (Model (Attr Shared position)))",
"cheapestOpen" => "Attr * (Attr * (Attr Shared position -> Attr * F64), Attr (* | a | b | c) (Model (Attr Shared position)) -> Attr * (Result (Attr Shared position) (Attr * [ KeyNotFound ]*)))",
"astar" => "Attr Shared (Attr Shared (Attr Shared position, Attr Shared position -> Attr * F64), Attr Shared (Attr Shared position -> Attr * (Set (Attr * position))), Attr Shared position, Attr Shared (Model (Attr Shared position)) -> Attr * [ Err (Attr * [ KeyNotFound ]*), Ok (Attr * (List (Attr Shared position))) ]*)",
},
);
}
#[test]
fn load_and_typecheck_quicksort() {
let subs_by_module = MutMap::default();
let loaded_module = load_fixture("interface_with_deps", "Quicksort", subs_by_module);
expect_types(
loaded_module,
hashmap! {
"swap" => "Attr * (Attr * I64, Attr * I64, Attr * (List (Attr Shared a)) -> Attr * (List (Attr Shared a)))",
"partition" => "Attr * (Attr Shared I64, Attr Shared I64, Attr b (List (Attr Shared (Num (Attr Shared a)))) -> Attr * [ Pair (Attr * I64) (Attr b (List (Attr Shared (Num (Attr Shared a))))) ])",
"partitionHelp" => "Attr Shared (Attr b I64, Attr Shared I64, Attr c (List (Attr Shared (Num (Attr Shared a)))), Attr Shared I64, Attr Shared (Num (Attr Shared a)) -> Attr * [ Pair (Attr b I64) (Attr c (List (Attr Shared (Num (Attr Shared a))))) ])",
"quicksort" => "Attr Shared (Attr b (List (Attr Shared (Num (Attr Shared a)))), Attr Shared I64, Attr Shared I64 -> Attr b (List (Attr Shared (Num (Attr Shared a)))))",
},
);
}
#[test]
fn quickcheck_nested_let() {
let subs_by_module = MutMap::default();
let loaded_module = load_fixture("app_with_deps", "QuicksortOneDef", subs_by_module);
expect_types(
loaded_module,
hashmap! {
"quicksort" => "Attr * (Attr b (List (Attr Shared (Num (Attr Shared a)))) -> Attr b (List (Attr Shared (Num (Attr Shared a)))))",
},
);
}
#[test]
fn load_principal_types() {
let subs_by_module = MutMap::default();
let loaded_module = load_fixture("no_deps", "Principal", subs_by_module);
expect_types(
loaded_module,
hashmap! {
"intVal" => "Attr * Str",
"identity" => "Attr * (a -> a)",
},
);
}
#[test]
#[ignore]
fn load_dep_types() {
let subs_by_module = MutMap::default();
let loaded_module = load_fixture("interface_with_deps", "Primary", subs_by_module);
// the inferred signature for withDefault is wrong, part of the alias in alias issue.
// "withDefault" => "Attr * (Attr * (Res.Res (Attr a b) (Attr * *)), Attr a b -> Attr a b)",
expect_types(
loaded_module,
hashmap! {
"blah2" => "Attr * F64",
"blah3" => "Attr * Str",
"str" => "Attr * Str",
"alwaysThree" => "Attr * (* -> Attr * Str)",
"identity" => "Attr * (a -> a)",
"z" => "Attr * Str",
"w" => "Attr * (Dep1.Identity (Attr * {}))",
"succeed" => "Attr * (Attr b a -> Attr * (Dep1.Identity (Attr b a)))",
"yay" => "Attr * (Res.Res (Attr * {}) (Attr * err))",
"withDefault" => "Attr * (Attr (* | b | c) (Res.Res (Attr b a) (Attr c *)), Attr b a -> Attr b a)",
},
);
}
#[test]
#[ignore]
fn load_custom_res() {
let subs_by_module = MutMap::default();
let loaded_module = load_fixture("interface_with_deps", "Res", subs_by_module);
expect_types(
loaded_module,
hashmap! {
"withDefault" =>"Attr * (Attr (* | b | c) (Res (Attr b a) (Attr c err)), Attr b a -> Attr b a)",
"map" => "Attr * (Attr (* | c | d) (Res (Attr c a) (Attr d err)), Attr * (Attr c a -> Attr e b) -> Attr * (Res (Attr e b) (Attr d err)))",
"andThen" => "Attr * (Attr (* | c | d) (Res (Attr c a) (Attr d err)), Attr * (Attr c a -> Attr f (Res (Attr e b) (Attr d err))) -> Attr f (Res (Attr e b) (Attr d err)))",
},
);
}
#[test]
fn imported_dep_regression() {
let subs_by_module = MutMap::default();
let loaded_module = load_fixture("interface_with_deps", "OneDep", subs_by_module);
expect_types(
loaded_module,
hashmap! {
"str" => "Attr * Str",
},
);
}
// #[test]
// fn load_records() {
// test_async(async {
// use roc::types::{ErrorType, Mismatch, Problem, TypeExt};
// let subs_by_module = MutMap::default();
// let loaded_module =
// load_fixture("interface_with_deps", "Records", subs_by_module);
// // NOTE: `a` here is unconstrained, so unifies with <type error>
// let expected_types = hashmap! {
// "Records.intVal" => "a",
// };
// let a = ErrorType::FlexVar("a".into());
// let mut record = SendMap::default();
// record.insert("x".into(), a);
// let problem = Problem::Mismatch(
// Mismatch::TypeMismatch,
// ErrorType::Record(SendMap::default(), TypeExt::Closed),
// ErrorType::Record(record, TypeExt::FlexOpen("b".into())),
// );
// assert_eq!(loaded_module.problems, vec![problem]);
// assert_eq!(expected_types.len(), loaded_module.declarations.len());
// let mut subs = loaded_module.solved.into_inner();
// for decl in loaded_module.declarations {
// let def = match decl {
// Declare(def) => def,
// rec_decl @ DeclareRec(_) => {
// panic!(
// "Unexpected recursive def in module declarations: {:?}",
// rec_decl
// );
// }
// cycle @ InvalidCycle(_, _) => {
// panic!("Unexpected cyclic def in module declarations: {:?}", cycle);
// }
// };
// for (symbol, expr_var) in def.pattern_vars {
// let content = subs.get(expr_var).content;
// name_all_type_vars(expr_var, &mut subs);
// let actual_str = content_to_string(content, &mut subs);
// let expected_type = expected_types.get(symbol.as_str()).unwrap_or_else(|| {
// panic!("Defs included an unexpected symbol: {:?}", symbol)
// });
// assert_eq!((&symbol, expected_type), (&symbol, &actual_str.as_str()));
// }
// }
// });
// }
}

View file

@ -59,6 +59,7 @@ pub enum LowLevel {
NumAcos, NumAcos,
NumAsin, NumAsin,
NumBitwiseAnd, NumBitwiseAnd,
NumBitwiseXor,
Eq, Eq,
NotEq, NotEq,
And, And,

View file

@ -738,6 +738,7 @@ define_builtins! {
10 INC: "#inc" // internal function that increments the refcount 10 INC: "#inc" // internal function that increments the refcount
11 DEC: "#dec" // internal function that increments the refcount 11 DEC: "#dec" // internal function that increments the refcount
12 ARG_CLOSURE: "#arg_closure" // symbol used to store the closure record 12 ARG_CLOSURE: "#arg_closure" // symbol used to store the closure record
13 LIST_EQ: "#list_eq" // internal function that checks list equality
} }
1 NUM: "Num" => { 1 NUM: "Num" => {
0 NUM_NUM: "Num" imported // the Num.Num type alias 0 NUM_NUM: "Num" imported // the Num.Num type alias
@ -822,10 +823,16 @@ define_builtins! {
79 NUM_AT_BINARY32: "@Binary32" 79 NUM_AT_BINARY32: "@Binary32"
80 NUM_BINARY32: "Binary32" imported 80 NUM_BINARY32: "Binary32" imported
81 NUM_BITWISE_AND: "bitwiseAnd" 81 NUM_BITWISE_AND: "bitwiseAnd"
82 NUM_SUB_WRAP: "subWrap" 82 NUM_BITWISE_XOR: "bitwiseXor"
83 NUM_SUB_CHECKED: "subChecked" 83 NUM_SUB_WRAP: "subWrap"
84 NUM_MUL_WRAP: "mulWrap" 84 NUM_SUB_CHECKED: "subChecked"
85 NUM_MUL_CHECKED: "mulChecked" 85 NUM_MUL_WRAP: "mulWrap"
86 NUM_MUL_CHECKED: "mulChecked"
87 NUM_INT: "Int" imported
88 NUM_FLOAT: "Float" imported
89 NUM_AT_NATURAL: "@Natural"
90 NUM_NATURAL: "Natural" imported
91 NUM_NAT: "Nat" imported
} }
2 BOOL: "Bool" => { 2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias

View file

@ -156,6 +156,10 @@ impl<'a> ParamMap<'a> {
Let(_, _, _, cont) => { Let(_, _, _, cont) => {
stack.push(cont); stack.push(cont);
} }
Invoke { pass, fail, .. } => {
stack.push(pass);
stack.push(fail);
}
Switch { Switch {
branches, branches,
default_branch, default_branch,
@ -166,7 +170,7 @@ impl<'a> ParamMap<'a> {
} }
Inc(_, _) | Dec(_, _) => unreachable!("these have not been introduced yet"), Inc(_, _) | Dec(_, _) => unreachable!("these have not been introduced yet"),
Ret(_) | Jump(_, _) | RuntimeError(_) => { Ret(_) | Rethrow | Jump(_, _) | RuntimeError(_) => {
// these are terminal, do nothing // these are terminal, do nothing
} }
} }
@ -295,6 +299,62 @@ impl<'a> BorrowInfState<'a> {
/// ///
/// and determines whether z and which of the symbols used in e /// and determines whether z and which of the symbols used in e
/// must be taken as owned paramters /// must be taken as owned paramters
fn collect_call(&mut self, z: Symbol, e: &crate::ir::Call<'a>) {
use crate::ir::CallType::*;
let crate::ir::Call {
call_type,
arguments,
} = e;
match call_type {
ByName {
name, arg_layouts, ..
}
| ByPointer {
name, arg_layouts, ..
} => {
// get the borrow signature of the applied function
let ps = match self.param_map.get_symbol(*name) {
Some(slice) => slice,
None => Vec::from_iter_in(
arg_layouts.iter().cloned().map(|layout| Param {
symbol: Symbol::UNDERSCORE,
borrow: false,
layout,
}),
self.arena,
)
.into_bump_slice(),
};
// the return value will be owned
self.own_var(z);
// if the function exects an owned argument (ps), the argument must be owned (args)
self.own_args_using_params(arguments, ps);
}
LowLevel { op } => {
// very unsure what demand RunLowLevel should place upon its arguments
self.own_var(z);
let ps = lowlevel_borrow_signature(self.arena, *op);
self.own_args_using_bools(arguments, ps);
}
Foreign { .. } => {
// very unsure what demand ForeignCall should place upon its arguments
self.own_var(z);
let ps = foreign_borrow_signature(self.arena, arguments.len());
self.own_args_using_bools(arguments, ps);
}
}
}
fn collect_expr(&mut self, z: Symbol, e: &Expr<'a>) { fn collect_expr(&mut self, z: Symbol, e: &Expr<'a>) {
use Expr::*; use Expr::*;
@ -334,74 +394,41 @@ impl<'a> BorrowInfState<'a> {
} }
} }
FunctionCall { Call(call) => self.collect_call(z, call),
call_type,
args,
arg_layouts,
..
} => {
// get the borrow signature of the applied function
let ps = match self.param_map.get_symbol(call_type.get_inner()) {
Some(slice) => slice,
None => Vec::from_iter_in(
arg_layouts.iter().cloned().map(|layout| Param {
symbol: Symbol::UNDERSCORE,
borrow: false,
layout,
}),
self.arena,
)
.into_bump_slice(),
};
// the return value will be owned
self.own_var(z);
// if the function exects an owned argument (ps), the argument must be owned (args)
self.own_args_using_params(args, ps);
}
RunLowLevel(op, args) => {
// very unsure what demand RunLowLevel should place upon its arguments
self.own_var(z);
let ps = lowlevel_borrow_signature(self.arena, *op);
self.own_args_using_bools(args, ps);
}
ForeignCall { arguments, .. } => {
// very unsure what demand ForeignCall should place upon its arguments
self.own_var(z);
let ps = foreign_borrow_signature(self.arena, arguments.len());
self.own_args_using_bools(arguments, ps);
}
Literal(_) | FunctionPointer(_, _) | RuntimeErrorFunction(_) => {} Literal(_) | FunctionPointer(_, _) | RuntimeErrorFunction(_) => {}
} }
} }
#[allow(clippy::many_single_char_names)]
fn preserve_tail_call(&mut self, x: Symbol, v: &Expr<'a>, b: &Stmt<'a>) { fn preserve_tail_call(&mut self, x: Symbol, v: &Expr<'a>, b: &Stmt<'a>) {
if let ( match (v, b) {
Expr::FunctionCall { (
call_type, Expr::Call(crate::ir::Call {
args: ys, call_type: crate::ir::CallType::ByName { name: g, .. },
arguments: ys,
.. ..
}, }),
Stmt::Ret(z), Stmt::Ret(z),
) = (v, b) )
{ | (
let g = call_type.get_inner(); Expr::Call(crate::ir::Call {
if self.current_proc == g && x == *z { call_type: crate::ir::CallType::ByPointer { name: g, .. },
arguments: ys,
..
}),
Stmt::Ret(z),
) => {
if self.current_proc == *g && x == *z {
// anonymous functions (for which the ps may not be known) // anonymous functions (for which the ps may not be known)
// can never be tail-recursive, so this is fine // can never be tail-recursive, so this is fine
if let Some(ps) = self.param_map.get_symbol(g) { if let Some(ps) = self.param_map.get_symbol(*g) {
self.own_params_using_args(ys, ps) self.own_params_using_args(ys, ps)
} }
} }
} }
_ => {}
}
} }
fn update_param_set(&mut self, ps: &[Param<'a>]) { fn update_param_set(&mut self, ps: &[Param<'a>]) {
@ -444,11 +471,29 @@ impl<'a> BorrowInfState<'a> {
self.collect_stmt(b); self.collect_stmt(b);
self.preserve_tail_call(*x, &Expr::FunctionPointer(*fsymbol, layout.clone()), b); self.preserve_tail_call(*x, &Expr::FunctionPointer(*fsymbol, layout.clone()), b);
} }
Let(x, v, _, b) => { Let(x, v, _, b) => {
self.collect_stmt(b); self.collect_stmt(b);
self.collect_expr(*x, v); self.collect_expr(*x, v);
self.preserve_tail_call(*x, v, b); self.preserve_tail_call(*x, v, b);
} }
Invoke {
symbol,
call,
layout: _,
pass,
fail,
} => {
self.collect_stmt(pass);
self.collect_stmt(fail);
self.collect_call(*symbol, call);
// TODO how to preserve the tail call of an invoke?
// self.preserve_tail_call(*x, v, b);
}
Jump(j, ys) => { Jump(j, ys) => {
let ps = self.param_map.get_join_point(*j); let ps = self.param_map.get_join_point(*j);
@ -470,7 +515,7 @@ impl<'a> BorrowInfState<'a> {
} }
Inc(_, _) | Dec(_, _) => unreachable!("these have not been introduced yet"), Inc(_, _) | Dec(_, _) => unreachable!("these have not been introduced yet"),
Ret(_) | RuntimeError(_) => { Ret(_) | RuntimeError(_) | Rethrow => {
// these are terminal, do nothing // these are terminal, do nothing
} }
} }
@ -533,9 +578,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap
| NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte
| NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt | NumBitwiseAnd => { | NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt | NumBitwiseAnd
arena.alloc_slice_copy(&[irrelevant, irrelevant]) | NumBitwiseXor => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
}
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor
| NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin => { | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin => {

View file

@ -66,7 +66,7 @@ pub enum Test<'a> {
union: crate::exhaustive::Union, union: crate::exhaustive::Union,
arguments: Vec<(Pattern<'a>, Layout<'a>)>, arguments: Vec<(Pattern<'a>, Layout<'a>)>,
}, },
IsInt(i64), IsInt(i128),
// float patterns are stored as u64 so they are comparable/hashable // float patterns are stored as u64 so they are comparable/hashable
IsFloat(u64), IsFloat(u64),
IsStr(Box<str>), IsStr(Box<str>),
@ -902,7 +902,10 @@ pub fn optimize_when<'a>(
let decision_tree = compile(patterns); let decision_tree = compile(patterns);
let decider = tree_to_decider(decision_tree); let decider = tree_to_decider(decision_tree);
let target_counts = count_targets(&decider);
// for each target (branch body), count in how many ways it can be reached
let mut target_counts = bumpalo::vec![in env.arena; 0; indexed_branches.len()];
count_targets(&mut target_counts, &decider);
let mut choices = MutMap::default(); let mut choices = MutMap::default();
let mut jumps = Vec::new(); let mut jumps = Vec::new();
@ -910,8 +913,9 @@ pub fn optimize_when<'a>(
for (index, branch) in indexed_branches.into_iter() { for (index, branch) in indexed_branches.into_iter() {
let ((branch_index, choice), opt_jump) = create_choices(&target_counts, index, branch); let ((branch_index, choice), opt_jump) = create_choices(&target_counts, index, branch);
if let Some(jump) = opt_jump { if let Some((index, body)) = opt_jump {
jumps.push(jump); let id = JoinPointId(env.unique_symbol());
jumps.push((index, id, body));
} }
choices.insert(branch_index, choice); choices.insert(branch_index, choice);
@ -919,7 +923,7 @@ pub fn optimize_when<'a>(
let choice_decider = insert_choices(&choices, decider); let choice_decider = insert_choices(&choices, decider);
decide_to_branching( let mut stmt = decide_to_branching(
env, env,
procs, procs,
layout_cache, layout_cache,
@ -928,7 +932,18 @@ pub fn optimize_when<'a>(
ret_layout, ret_layout,
choice_decider, choice_decider,
&jumps, &jumps,
) );
for (_, id, body) in jumps.into_iter() {
stmt = Stmt::Join {
id,
parameters: &[],
continuation: env.arena.alloc(body),
remainder: env.arena.alloc(stmt),
};
}
stmt
} }
#[derive(Debug)] #[derive(Debug)]
@ -1092,7 +1107,9 @@ fn test_to_equality<'a>(
) )
} }
Test::IsInt(test_int) => { Test::IsInt(test_int) => {
let lhs = Expr::Literal(Literal::Int(test_int)); // TODO don't downcast i128 here
debug_assert!(test_int <= i64::MAX as i128);
let lhs = Expr::Literal(Literal::Int(test_int as i64));
let lhs_symbol = env.unique_symbol(); let lhs_symbol = env.unique_symbol();
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int64), lhs)); stores.push((lhs_symbol, Layout::Builtin(Builtin::Int64), lhs));
@ -1277,7 +1294,10 @@ fn compile_test<'a>(
ret_layout, ret_layout,
); );
let test = Expr::RunLowLevel(LowLevel::Eq, arena.alloc([lhs, rhs])); let test = Expr::Call(crate::ir::Call {
call_type: crate::ir::CallType::LowLevel { op: LowLevel::Eq },
arguments: arena.alloc([lhs, rhs]),
});
// write to the test symbol // write to the test symbol
cond = Stmt::Let( cond = Stmt::Let(
@ -1328,7 +1348,7 @@ fn decide_to_branching<'a>(
cond_layout: Layout<'a>, cond_layout: Layout<'a>,
ret_layout: Layout<'a>, ret_layout: Layout<'a>,
decider: Decider<'a, Choice<'a>>, decider: Decider<'a, Choice<'a>>,
jumps: &Vec<(u64, Stmt<'a>)>, jumps: &Vec<(u64, JoinPointId, Stmt<'a>)>,
) -> Stmt<'a> { ) -> Stmt<'a> {
use Choice::*; use Choice::*;
use Decider::*; use Decider::*;
@ -1337,12 +1357,11 @@ fn decide_to_branching<'a>(
match decider { match decider {
Leaf(Jump(label)) => { Leaf(Jump(label)) => {
// we currently inline the jumps: does fewer jumps but produces a larger artifact let index = jumps
let (_, expr) = jumps .binary_search_by_key(&label, |ref r| r.0)
.iter()
.find(|(l, _)| l == &label)
.expect("jump not in list of jumps"); .expect("jump not in list of jumps");
expr.clone()
Stmt::Jump(jumps[index].1, &[])
} }
Leaf(Inline(expr)) => expr, Leaf(Inline(expr)) => expr,
Chain { Chain {
@ -1619,39 +1638,32 @@ fn to_chain<'a>(
/// If a target appears exactly once in a Decider, the corresponding expression /// If a target appears exactly once in a Decider, the corresponding expression
/// can be inlined. Whether things are inlined or jumps is called a "choice". /// can be inlined. Whether things are inlined or jumps is called a "choice".
fn count_targets(decision_tree: &Decider<u64>) -> MutMap<u64, u64> { fn count_targets(targets: &mut bumpalo::collections::Vec<u64>, initial: &Decider<u64>) {
let mut result = MutMap::default();
count_targets_help(decision_tree, &mut result);
result
}
fn count_targets_help(decision_tree: &Decider<u64>, targets: &mut MutMap<u64, u64>) {
use Decider::*; use Decider::*;
let mut stack = vec![initial];
while let Some(decision_tree) = stack.pop() {
match decision_tree { match decision_tree {
Leaf(target) => match targets.get_mut(target) { Leaf(target) => {
None => { targets[*target as usize] += 1;
targets.insert(*target, 1);
} }
Some(current) => {
*current += 1;
}
},
Chain { Chain {
success, failure, .. success, failure, ..
} => { } => {
count_targets_help(success, targets); stack.push(success);
count_targets_help(failure, targets); stack.push(failure);
} }
FanOut { FanOut {
tests, fallback, .. tests, fallback, ..
} => { } => {
count_targets_help(fallback, targets); stack.push(fallback);
for (_, decider) in tests { for (_, decider) in tests {
count_targets_help(decider, targets); stack.push(decider);
}
} }
} }
} }
@ -1659,11 +1671,11 @@ fn count_targets_help(decision_tree: &Decider<u64>, targets: &mut MutMap<u64, u6
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
fn create_choices<'a>( fn create_choices<'a>(
target_counts: &MutMap<u64, u64>, target_counts: &bumpalo::collections::Vec<'a, u64>,
target: u64, target: u64,
branch: Stmt<'a>, branch: Stmt<'a>,
) -> ((u64, Choice<'a>), Option<(u64, Stmt<'a>)>) { ) -> ((u64, Choice<'a>), Option<(u64, Stmt<'a>)>) {
match target_counts.get(&target) { match target_counts.get(target as usize) {
None => unreachable!( None => unreachable!(
"this should never happen: {:?} not in {:?}", "this should never happen: {:?} not in {:?}",
target, target_counts target, target_counts

View file

@ -37,7 +37,7 @@ pub enum Pattern {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Literal { pub enum Literal {
Int(i64), Int(i128),
Bit(bool), Bit(bool),
Byte(u8), Byte(u8),
Float(u64), Float(u64),

View file

@ -31,10 +31,27 @@ pub fn occuring_variables(stmt: &Stmt<'_>) -> (MutSet<Symbol>, MutSet<Symbol>) {
bound_variables.insert(*symbol); bound_variables.insert(*symbol);
stack.push(cont); stack.push(cont);
} }
Invoke {
symbol,
call,
pass,
fail,
..
} => {
occuring_variables_call(call, &mut result);
result.insert(*symbol);
bound_variables.insert(*symbol);
stack.push(pass);
stack.push(fail);
}
Ret(symbol) => { Ret(symbol) => {
result.insert(*symbol); result.insert(*symbol);
} }
Rethrow => {}
Inc(symbol, cont) | Dec(symbol, cont) => { Inc(symbol, cont) | Dec(symbol, cont) => {
result.insert(*symbol); result.insert(*symbol);
stack.push(cont); stack.push(cont);
@ -75,6 +92,12 @@ pub fn occuring_variables(stmt: &Stmt<'_>) -> (MutSet<Symbol>, MutSet<Symbol>) {
(result, bound_variables) (result, bound_variables)
} }
fn occuring_variables_call(call: &crate::ir::Call<'_>, result: &mut MutSet<Symbol>) {
// NOTE though the function name does occur, it is a static constant in the program
// for liveness, it should not be included here.
result.extend(call.arguments.iter().copied());
}
pub fn occuring_variables_expr(expr: &Expr<'_>, result: &mut MutSet<Symbol>) { pub fn occuring_variables_expr(expr: &Expr<'_>, result: &mut MutSet<Symbol>) {
use Expr::*; use Expr::*;
@ -86,11 +109,7 @@ pub fn occuring_variables_expr(expr: &Expr<'_>, result: &mut MutSet<Symbol>) {
result.insert(*symbol); result.insert(*symbol);
} }
FunctionCall { args, .. } => { Call(call) => occuring_variables_call(call, result),
// NOTE thouth the function name does occur, it is a static constant in the program
// for liveness, it should not be included here.
result.extend(args.iter().copied());
}
Tag { arguments, .. } Tag { arguments, .. }
| Struct(arguments) | Struct(arguments)
@ -108,12 +127,6 @@ pub fn occuring_variables_expr(expr: &Expr<'_>, result: &mut MutSet<Symbol>) {
Reset(x) => { Reset(x) => {
result.insert(*x); result.insert(*x);
} }
RunLowLevel(_, args) => {
result.extend(args.iter());
}
ForeignCall { arguments, .. } => {
result.extend(arguments.iter());
}
EmptyArray | RuntimeErrorFunction(_) | Literal(_) => {} EmptyArray | RuntimeErrorFunction(_) | Literal(_) => {}
} }
@ -208,6 +221,11 @@ fn consume_expr(m: &VarMap, e: &Expr<'_>) -> bool {
} }
} }
fn consume_call(_: &VarMap, _: &crate::ir::Call<'_>) -> bool {
// variables bound by a call (or invoke) must always be consumed
true
}
impl<'a> Context<'a> { impl<'a> Context<'a> {
pub fn new(arena: &'a Bump, param_map: &'a ParamMap<'a>) -> Self { pub fn new(arena: &'a Bump, param_map: &'a ParamMap<'a>) -> Self {
let mut vars = MutMap::default(); let mut vars = MutMap::default();
@ -410,6 +428,75 @@ impl<'a> Context<'a> {
b b
} }
fn visit_call(
&self,
z: Symbol,
call_type: crate::ir::CallType<'a>,
arguments: &'a [Symbol],
l: Layout<'a>,
b: &'a Stmt<'a>,
b_live_vars: &LiveVarSet,
) -> &'a Stmt<'a> {
use crate::ir::CallType::*;
match &call_type {
LowLevel { op } => {
let ps = crate::borrow::lowlevel_borrow_signature(self.arena, *op);
let b = self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars);
let v = Expr::Call(crate::ir::Call {
call_type,
arguments,
});
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
Foreign { .. } => {
let ps = crate::borrow::foreign_borrow_signature(self.arena, arguments.len());
let b = self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars);
let v = Expr::Call(crate::ir::Call {
call_type,
arguments,
});
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
ByName {
name, arg_layouts, ..
}
| ByPointer {
name, arg_layouts, ..
} => {
// get the borrow signature
let ps = match self.param_map.get_symbol(*name) {
Some(slice) => slice,
None => Vec::from_iter_in(
arg_layouts.iter().cloned().map(|layout| Param {
symbol: Symbol::UNDERSCORE,
borrow: false,
layout,
}),
self.arena,
)
.into_bump_slice(),
};
let v = Expr::Call(crate::ir::Call {
call_type,
arguments,
});
let b = self.add_dec_after_application(arguments, ps, b, b_live_vars);
let b = self.arena.alloc(Stmt::Let(z, v, l, b));
self.add_inc_before(arguments, ps, b, b_live_vars)
}
}
}
#[allow(clippy::many_single_char_names)] #[allow(clippy::many_single_char_names)]
fn visit_variable_declaration( fn visit_variable_declaration(
&self, &self,
@ -445,45 +532,10 @@ impl<'a> Context<'a> {
self.arena.alloc(Stmt::Let(z, v, l, b)) self.arena.alloc(Stmt::Let(z, v, l, b))
} }
RunLowLevel(op, args) => { Call(crate::ir::Call {
let ps = crate::borrow::lowlevel_borrow_signature(self.arena, op);
let b = self.add_dec_after_lowlevel(args, ps, b, b_live_vars);
self.arena.alloc(Stmt::Let(z, v, l, b))
}
ForeignCall { arguments, .. } => {
let ps = crate::borrow::foreign_borrow_signature(self.arena, arguments.len());
let b = self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars);
self.arena.alloc(Stmt::Let(z, v, l, b))
}
FunctionCall {
args: ys,
arg_layouts,
call_type, call_type,
.. arguments,
} => { }) => self.visit_call(z, call_type, arguments, l, b, b_live_vars),
// get the borrow signature
let ps = match self.param_map.get_symbol(call_type.get_inner()) {
Some(slice) => slice,
None => Vec::from_iter_in(
arg_layouts.iter().cloned().map(|layout| Param {
symbol: Symbol::UNDERSCORE,
borrow: false,
layout,
}),
self.arena,
)
.into_bump_slice(),
};
let b = self.add_dec_after_application(ys, ps, b, b_live_vars);
let b = self.arena.alloc(Stmt::Let(z, v, l, b));
self.add_inc_before(ys, ps, b, b_live_vars)
}
EmptyArray EmptyArray
| FunctionPointer(_, _) | FunctionPointer(_, _)
@ -499,21 +551,45 @@ impl<'a> Context<'a> {
(new_b, live_vars) (new_b, live_vars)
} }
fn update_var_info_invoke(
&self,
symbol: Symbol,
layout: &Layout<'a>,
call: &crate::ir::Call<'a>,
) -> Self {
// is this value a constant?
// TODO do function pointers also fall into this category?
let persistent = call.arguments.is_empty();
// must this value be consumed?
let consume = consume_call(&self.vars, call);
self.update_var_info_help(symbol, layout, persistent, consume)
}
fn update_var_info(&self, symbol: Symbol, layout: &Layout<'a>, expr: &Expr<'a>) -> Self { fn update_var_info(&self, symbol: Symbol, layout: &Layout<'a>, expr: &Expr<'a>) -> Self {
let mut ctx = self.clone();
// can this type be reference-counted at runtime?
let reference = layout.contains_refcounted();
// is this value a constant? // is this value a constant?
// TODO do function pointers also fall into this category? // TODO do function pointers also fall into this category?
let persistent = match expr { let persistent = match expr {
Expr::FunctionCall { args, .. } => args.is_empty(), Expr::Call(crate::ir::Call { arguments, .. }) => arguments.is_empty(),
_ => false, _ => false,
}; };
// must this value be consumed? // must this value be consumed?
let consume = consume_expr(&ctx.vars, expr); let consume = consume_expr(&self.vars, expr);
self.update_var_info_help(symbol, layout, persistent, consume)
}
fn update_var_info_help(
&self,
symbol: Symbol,
layout: &Layout<'a>,
persistent: bool,
consume: bool,
) -> Self {
// can this type be reference-counted at runtime?
let reference = layout.contains_refcounted();
let info = VarInfo { let info = VarInfo {
reference, reference,
@ -521,6 +597,8 @@ impl<'a> Context<'a> {
consume, consume,
}; };
let mut ctx = self.clone();
ctx.vars.insert(symbol, info); ctx.vars.insert(symbol, info);
ctx ctx
@ -628,6 +706,47 @@ impl<'a> Context<'a> {
) )
} }
Invoke {
symbol,
call,
pass,
fail,
layout,
} => {
// TODO this combines parts of Let and Switch. Did this happen correctly?
let mut case_live_vars = collect_stmt(stmt, &self.jp_live_vars, MutSet::default());
case_live_vars.remove(symbol);
let fail = {
// TODO should we use ctor info like Lean?
let ctx = self.clone();
let (b, alt_live_vars) = ctx.visit_stmt(fail);
ctx.add_dec_for_alt(&case_live_vars, &alt_live_vars, b)
};
case_live_vars.insert(*symbol);
let pass = {
// TODO should we use ctor info like Lean?
let ctx = self.clone();
let ctx = ctx.update_var_info_invoke(*symbol, layout, call);
let (b, alt_live_vars) = ctx.visit_stmt(pass);
ctx.add_dec_for_alt(&case_live_vars, &alt_live_vars, b)
};
let invoke = Invoke {
symbol: *symbol,
call: call.clone(),
pass,
fail,
layout: layout.clone(),
};
let stmt = self.arena.alloc(invoke);
(stmt, case_live_vars)
}
Join { Join {
id: j, id: j,
parameters: _, parameters: _,
@ -673,6 +792,8 @@ impl<'a> Context<'a> {
} }
} }
Rethrow => (stmt, MutSet::default()),
Jump(j, xs) => { Jump(j, xs) => {
let empty = MutSet::default(); let empty = MutSet::default();
let j_live_vars = match self.jp_live_vars.get(j) { let j_live_vars = match self.jp_live_vars.get(j) {
@ -757,6 +878,25 @@ pub fn collect_stmt(
vars vars
} }
Invoke {
symbol,
call,
pass,
fail,
..
} => {
vars = collect_stmt(pass, jp_live_vars, vars);
vars = collect_stmt(fail, jp_live_vars, vars);
vars.remove(symbol);
let mut result = MutSet::default();
occuring_variables_call(call, &mut result);
vars.extend(result);
vars
}
Ret(symbol) => { Ret(symbol) => {
vars.insert(*symbol); vars.insert(*symbol);
vars vars
@ -813,6 +953,8 @@ pub fn collect_stmt(
vars vars
} }
Rethrow => vars,
RuntimeError(_) => vars, RuntimeError(_) => vars,
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,7 @@ pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::<u8>() * 8) as usize;
/// If a (Num *) gets translated to a Layout, this is the numeric type it defaults to. /// If a (Num *) gets translated to a Layout, this is the numeric type it defaults to.
const DEFAULT_NUM_BUILTIN: Builtin<'_> = Builtin::Int64; const DEFAULT_NUM_BUILTIN: Builtin<'_> = Builtin::Int64;
pub const TAG_SIZE: Builtin<'_> = Builtin::Int64;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum LayoutProblem { pub enum LayoutProblem {
@ -157,7 +158,13 @@ impl<'a> ClosureLayout<'a> {
Ok(Some(closure_layout)) Ok(Some(closure_layout))
} }
Wrapped(tags) => { Wrapped {
sorted_tag_layouts: tags,
is_recursive,
} => {
// TODO handle recursive closures
debug_assert!(!is_recursive);
let closure_layout = let closure_layout =
ClosureLayout::from_tag_union(arena, tags.into_bump_slice()); ClosureLayout::from_tag_union(arena, tags.into_bump_slice());
@ -312,6 +319,7 @@ pub enum Builtin<'a> {
Int16, Int16,
Int8, Int8,
Int1, Int1,
Usize,
Float128, Float128,
Float64, Float64,
Float32, Float32,
@ -362,14 +370,60 @@ impl<'a> Layout<'a> {
} }
Structure(flat_type) => layout_from_flat_type(env, flat_type), Structure(flat_type) => layout_from_flat_type(env, flat_type),
// Ints
Alias(Symbol::NUM_I128, args, _) => {
debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Int128))
}
Alias(Symbol::NUM_I64, args, _) => { Alias(Symbol::NUM_I64, args, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Int64)) Ok(Layout::Builtin(Builtin::Int64))
} }
Alias(Symbol::NUM_I32, args, _) => {
debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Int32))
}
Alias(Symbol::NUM_I16, args, _) => {
debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Int16))
}
Alias(Symbol::NUM_I8, args, _) => {
debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Int8))
}
// I think unsigned and signed use the same layout
Alias(Symbol::NUM_U128, args, _) => {
debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Int128))
}
Alias(Symbol::NUM_U64, args, _) => {
debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Int64))
}
Alias(Symbol::NUM_U32, args, _) => {
debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Int32))
}
Alias(Symbol::NUM_U16, args, _) => {
debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Int16))
}
Alias(Symbol::NUM_U8, args, _) => {
debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Int8))
}
// Floats
Alias(Symbol::NUM_F64, args, _) => { Alias(Symbol::NUM_F64, args, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Float64)) Ok(Layout::Builtin(Builtin::Float64))
} }
Alias(Symbol::NUM_F32, args, _) => {
debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Float32))
}
Alias(_, _, var) => Self::from_var(env, var), Alias(_, _, var) => Self::from_var(env, var),
Error => Err(LayoutProblem::Erroneous), Error => Err(LayoutProblem::Erroneous),
} }
@ -654,6 +708,7 @@ impl<'a> Builtin<'a> {
const I16_SIZE: u32 = std::mem::size_of::<i16>() as u32; const I16_SIZE: u32 = std::mem::size_of::<i16>() as u32;
const I8_SIZE: u32 = std::mem::size_of::<i8>() as u32; const I8_SIZE: u32 = std::mem::size_of::<i8>() as u32;
const I1_SIZE: u32 = std::mem::size_of::<bool>() as u32; const I1_SIZE: u32 = std::mem::size_of::<bool>() as u32;
const USIZE_SIZE: u32 = std::mem::size_of::<usize>() as u32;
const F128_SIZE: u32 = 16; const F128_SIZE: u32 = 16;
const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32; const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32;
const F32_SIZE: u32 = std::mem::size_of::<f32>() as u32; const F32_SIZE: u32 = std::mem::size_of::<f32>() as u32;
@ -682,6 +737,7 @@ impl<'a> Builtin<'a> {
Int16 => Builtin::I16_SIZE, Int16 => Builtin::I16_SIZE,
Int8 => Builtin::I8_SIZE, Int8 => Builtin::I8_SIZE,
Int1 => Builtin::I1_SIZE, Int1 => Builtin::I1_SIZE,
Usize => Builtin::USIZE_SIZE,
Float128 => Builtin::F128_SIZE, Float128 => Builtin::F128_SIZE,
Float64 => Builtin::F64_SIZE, Float64 => Builtin::F64_SIZE,
Float32 => Builtin::F32_SIZE, Float32 => Builtin::F32_SIZE,
@ -707,6 +763,7 @@ impl<'a> Builtin<'a> {
Int16 => align_of::<i16>() as u32, Int16 => align_of::<i16>() as u32,
Int8 => align_of::<i8>() as u32, Int8 => align_of::<i8>() as u32,
Int1 => align_of::<bool>() as u32, Int1 => align_of::<bool>() as u32,
Usize => align_of::<usize>() as u32,
Float128 => align_of::<i128>() as u32, Float128 => align_of::<i128>() as u32,
Float64 => align_of::<f64>() as u32, Float64 => align_of::<f64>() as u32,
Float32 => align_of::<f32>() as u32, Float32 => align_of::<f32>() as u32,
@ -722,7 +779,7 @@ impl<'a> Builtin<'a> {
use Builtin::*; use Builtin::*;
match self { match self {
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Float128 | Float64 | Float32 Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Float128 | Float64 | Float32
| Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => true, | Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => true,
Str | Dict(_, _) | Set(_) | List(_, _) => false, Str | Dict(_, _) | Set(_) | List(_, _) => false,
} }
@ -733,7 +790,7 @@ impl<'a> Builtin<'a> {
use Builtin::*; use Builtin::*;
match self { match self {
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Float128 | Float64 | Float32 Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Float128 | Float64 | Float32
| Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => false, | Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => false,
List(mode, element_layout) => match mode { List(mode, element_layout) => match mode {
MemoryMode::Refcounted => true, MemoryMode::Refcounted => true,
@ -757,14 +814,64 @@ fn layout_from_flat_type<'a>(
match flat_type { match flat_type {
Apply(symbol, args) => { Apply(symbol, args) => {
match symbol { match symbol {
// Ints
Symbol::NUM_NAT => {
debug_assert_eq!(args.len(), 0);
Ok(Layout::Builtin(Builtin::Usize))
}
Symbol::NUM_I128 => {
debug_assert_eq!(args.len(), 0);
Ok(Layout::Builtin(Builtin::Int128))
}
Symbol::NUM_I64 => { Symbol::NUM_I64 => {
debug_assert_eq!(args.len(), 0); debug_assert_eq!(args.len(), 0);
Ok(Layout::Builtin(Builtin::Int64)) Ok(Layout::Builtin(Builtin::Int64))
} }
Symbol::NUM_I32 => {
debug_assert_eq!(args.len(), 0);
Ok(Layout::Builtin(Builtin::Int32))
}
Symbol::NUM_I16 => {
debug_assert_eq!(args.len(), 0);
Ok(Layout::Builtin(Builtin::Int16))
}
Symbol::NUM_I8 => {
debug_assert_eq!(args.len(), 0);
Ok(Layout::Builtin(Builtin::Int8))
}
Symbol::NUM_U128 => {
debug_assert_eq!(args.len(), 0);
Ok(Layout::Builtin(Builtin::Int128))
}
Symbol::NUM_U64 => {
debug_assert_eq!(args.len(), 0);
Ok(Layout::Builtin(Builtin::Int64))
}
Symbol::NUM_U32 => {
debug_assert_eq!(args.len(), 0);
Ok(Layout::Builtin(Builtin::Int32))
}
Symbol::NUM_U16 => {
debug_assert_eq!(args.len(), 0);
Ok(Layout::Builtin(Builtin::Int16))
}
Symbol::NUM_U8 => {
debug_assert_eq!(args.len(), 0);
Ok(Layout::Builtin(Builtin::Int8))
}
// Floats
Symbol::NUM_F64 => { Symbol::NUM_F64 => {
debug_assert_eq!(args.len(), 0); debug_assert_eq!(args.len(), 0);
Ok(Layout::Builtin(Builtin::Float64)) Ok(Layout::Builtin(Builtin::Float64))
} }
Symbol::NUM_F32 => {
debug_assert_eq!(args.len(), 0);
Ok(Layout::Builtin(Builtin::Float32))
}
Symbol::NUM_NUM | Symbol::NUM_AT_NUM => { Symbol::NUM_NUM | Symbol::NUM_AT_NUM => {
// Num.Num should only ever have 1 argument, e.g. Num.Num Int.Integer // Num.Num should only ever have 1 argument, e.g. Num.Num Int.Integer
debug_assert_eq!(args.len(), 1); debug_assert_eq!(args.len(), 1);
@ -774,6 +881,7 @@ fn layout_from_flat_type<'a>(
layout_from_num_content(content) layout_from_num_content(content)
} }
Symbol::STR_STR => Ok(Layout::Builtin(Builtin::Str)), Symbol::STR_STR => Ok(Layout::Builtin(Builtin::Str)),
Symbol::LIST_LIST => list_layout_from_elem(env, args[0]), Symbol::LIST_LIST => list_layout_from_elem(env, args[0]),
Symbol::ATTR_ATTR => { Symbol::ATTR_ATTR => {
@ -888,7 +996,7 @@ fn layout_from_flat_type<'a>(
let mut tag_layout = Vec::with_capacity_in(variables.len() + 1, arena); let mut tag_layout = Vec::with_capacity_in(variables.len() + 1, arena);
// store the discriminant // store the discriminant
tag_layout.push(Layout::Builtin(Builtin::Int64)); tag_layout.push(Layout::Builtin(TAG_SIZE));
for var in variables { for var in variables {
// TODO does this cause problems with mutually recursive unions? // TODO does this cause problems with mutually recursive unions?
@ -993,10 +1101,16 @@ pub enum UnionVariant<'a> {
Never, Never,
Unit, Unit,
UnitWithArguments, UnitWithArguments,
BoolUnion { ttrue: TagName, ffalse: TagName }, BoolUnion {
ttrue: TagName,
ffalse: TagName,
},
ByteUnion(Vec<'a, TagName>), ByteUnion(Vec<'a, TagName>),
Unwrapped(Vec<'a, Layout<'a>>), Unwrapped(Vec<'a, Layout<'a>>),
Wrapped(Vec<'a, (TagName, &'a [Layout<'a>])>), Wrapped {
sorted_tag_layouts: Vec<'a, (TagName, &'a [Layout<'a>])>,
is_recursive: bool,
},
} }
pub fn union_sorted_tags<'a>(arena: &'a Bump, var: Variable, subs: &Subs) -> UnionVariant<'a> { pub fn union_sorted_tags<'a>(arena: &'a Bump, var: Variable, subs: &Subs) -> UnionVariant<'a> {
@ -1119,7 +1233,7 @@ pub fn union_sorted_tags_help<'a>(
let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, arena); let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, arena);
// add the tag discriminant (size currently always hardcoded to i64) // add the tag discriminant (size currently always hardcoded to i64)
arg_layouts.push(Layout::Builtin(Builtin::Int64)); arg_layouts.push(Layout::Builtin(TAG_SIZE));
for var in arguments { for var in arguments {
match Layout::from_var(&mut env, var) { match Layout::from_var(&mut env, var) {
@ -1175,7 +1289,10 @@ pub fn union_sorted_tags_help<'a>(
UnionVariant::ByteUnion(tag_names) UnionVariant::ByteUnion(tag_names)
} }
_ => UnionVariant::Wrapped(answer), _ => UnionVariant::Wrapped {
sorted_tag_layouts: answer,
is_recursive: opt_rec_var.is_some(),
},
} }
} }
} }
@ -1214,17 +1331,25 @@ pub fn layout_from_tag_union<'a>(
Layout::Struct(field_layouts.into_bump_slice()) Layout::Struct(field_layouts.into_bump_slice())
} }
} }
Wrapped(tags) => { Wrapped {
sorted_tag_layouts: tags,
is_recursive,
} => {
let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena);
for (_, tag_layout) in tags { for (_, tag_layout) in tags {
tag_layouts.push(tag_layout); tag_layouts.push(tag_layout);
} }
if is_recursive {
Layout::RecursiveUnion(tag_layouts.into_bump_slice())
} else {
Layout::Union(tag_layouts.into_bump_slice()) Layout::Union(tag_layouts.into_bump_slice())
} }
} }
} }
} }
}
} }
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
@ -1257,8 +1382,27 @@ fn layout_from_num_content<'a>(content: Content) -> Result<Layout<'a>, LayoutPro
Ok(Layout::Builtin(DEFAULT_NUM_BUILTIN)) Ok(Layout::Builtin(DEFAULT_NUM_BUILTIN))
} }
Structure(Apply(symbol, args)) => match symbol { Structure(Apply(symbol, args)) => match symbol {
// Ints
Symbol::NUM_NAT => Ok(Layout::Builtin(Builtin::Usize)),
Symbol::NUM_INTEGER => Ok(Layout::Builtin(Builtin::Int64)), Symbol::NUM_INTEGER => Ok(Layout::Builtin(Builtin::Int64)),
Symbol::NUM_I128 => Ok(Layout::Builtin(Builtin::Int128)),
Symbol::NUM_I64 => Ok(Layout::Builtin(Builtin::Int64)),
Symbol::NUM_I32 => Ok(Layout::Builtin(Builtin::Int32)),
Symbol::NUM_I16 => Ok(Layout::Builtin(Builtin::Int16)),
Symbol::NUM_I8 => Ok(Layout::Builtin(Builtin::Int8)),
Symbol::NUM_U128 => Ok(Layout::Builtin(Builtin::Int128)),
Symbol::NUM_U64 => Ok(Layout::Builtin(Builtin::Int64)),
Symbol::NUM_U32 => Ok(Layout::Builtin(Builtin::Int32)),
Symbol::NUM_U16 => Ok(Layout::Builtin(Builtin::Int16)),
Symbol::NUM_U8 => Ok(Layout::Builtin(Builtin::Int8)),
// Floats
Symbol::NUM_FLOATINGPOINT => Ok(Layout::Builtin(Builtin::Float64)), Symbol::NUM_FLOATINGPOINT => Ok(Layout::Builtin(Builtin::Float64)),
Symbol::NUM_F64 => Ok(Layout::Builtin(Builtin::Float64)),
Symbol::NUM_F32 => Ok(Layout::Builtin(Builtin::Float32)),
_ => { _ => {
panic!( panic!(
"Invalid Num.Num type application: {:?}", "Invalid Num.Num type application: {:?}",
@ -1293,17 +1437,63 @@ fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result<Layout<'a>, LayoutPr
Content::Alias(Symbol::NUM_INTEGER, args, _) => { Content::Alias(Symbol::NUM_INTEGER, args, _) => {
debug_assert!(args.len() == 1); debug_assert!(args.len() == 1);
// TODO: we probably need to match on the type of the arg let (_, precision_var) = args[0];
// and return the correct builtin ex: Builtin::{Int32, Int16}
let precision = subs.get_without_compacting(precision_var).content;
match precision {
Content::Alias(symbol, args, _) => {
debug_assert!(args.is_empty());
let builtin = match symbol {
Symbol::NUM_SIGNED128 => Builtin::Int128,
Symbol::NUM_SIGNED64 => Builtin::Int64,
Symbol::NUM_SIGNED32 => Builtin::Int32,
Symbol::NUM_SIGNED16 => Builtin::Int16,
Symbol::NUM_SIGNED8 => Builtin::Int8,
Symbol::NUM_UNSIGNED128 => Builtin::Int128,
Symbol::NUM_UNSIGNED64 => Builtin::Int64,
Symbol::NUM_UNSIGNED32 => Builtin::Int32,
Symbol::NUM_UNSIGNED16 => Builtin::Int16,
Symbol::NUM_UNSIGNED8 => Builtin::Int8,
Symbol::NUM_NATURAL => Builtin::Usize,
_ => unreachable!("not a valid int variant: {:?} {:?}", symbol, args),
};
Ok(Layout::Builtin(builtin))
}
Content::FlexVar(_) | Content::RigidVar(_) => {
// default to i64
Ok(Layout::Builtin(Builtin::Int64)) Ok(Layout::Builtin(Builtin::Int64))
} }
_ => unreachable!("not a valid int variant: {:?}", precision),
}
}
Content::Alias(Symbol::NUM_FLOATINGPOINT, args, _) => { Content::Alias(Symbol::NUM_FLOATINGPOINT, args, _) => {
debug_assert!(args.len() == 1); debug_assert!(args.len() == 1);
// TODO: we probably need to match on the type of the arg let (_, precision_var) = args[0];
// and return the correct builtin ex: Builtin::Float32
let precision = subs.get_without_compacting(precision_var).content;
match precision {
Content::Alias(Symbol::NUM_BINARY32, args, _) => {
debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Float32))
}
Content::Alias(Symbol::NUM_BINARY64, args, _) => {
debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Float64)) Ok(Layout::Builtin(Builtin::Float64))
} }
Content::FlexVar(_) | Content::RigidVar(_) => {
// default to f64
Ok(Layout::Builtin(Builtin::Float64))
}
_ => unreachable!("not a valid float variant: {:?}", precision),
}
}
Content::FlexVar(_) | Content::RigidVar(_) => { Content::FlexVar(_) | Content::RigidVar(_) => {
// If this was still a (Num *) then default to compiling it to i64 // If this was still a (Num *) then default to compiling it to i64
Ok(Layout::Builtin(DEFAULT_NUM_BUILTIN)) Ok(Layout::Builtin(DEFAULT_NUM_BUILTIN))

View file

@ -75,17 +75,38 @@ fn insert_jumps<'a>(
match stmt { match stmt {
Let( Let(
symbol, symbol,
Expr::FunctionCall { Expr::Call(crate::ir::Call {
call_type: CallType::ByName(fsym), call_type: CallType::ByName { name: fsym, .. },
args, arguments,
.. ..
}, }),
_, _,
Stmt::Ret(rsym), Stmt::Ret(rsym),
) if needle == *fsym && symbol == rsym => { ) if needle == *fsym && symbol == rsym => {
// replace the call and return with a jump // replace the call and return with a jump
let jump = Stmt::Jump(goal_id, args); let jump = Stmt::Jump(goal_id, arguments);
Some(arena.alloc(jump))
}
Invoke {
symbol,
call:
crate::ir::Call {
call_type: CallType::ByName { name: fsym, .. },
arguments,
..
},
fail,
pass: Stmt::Ret(rsym),
..
} if needle == *fsym && symbol == rsym => {
debug_assert_eq!(fail, &&Stmt::Rethrow);
// replace the call and return with a jump
let jump = Stmt::Jump(goal_id, arguments);
Some(arena.alloc(jump)) Some(arena.alloc(jump))
} }
@ -101,6 +122,35 @@ fn insert_jumps<'a>(
None None
} }
} }
Invoke {
symbol,
call,
fail,
pass,
layout,
} => {
let opt_pass = insert_jumps(arena, pass, goal_id, needle);
let opt_fail = insert_jumps(arena, fail, goal_id, needle);
if opt_pass.is_some() || opt_fail.is_some() {
let pass = opt_pass.unwrap_or(pass);
let fail = opt_fail.unwrap_or(fail);
let stmt = Invoke {
symbol: *symbol,
call: call.clone(),
layout: layout.clone(),
pass,
fail,
};
Some(arena.alloc(stmt))
} else {
None
}
}
Join { Join {
id, id,
parameters, parameters,
@ -187,6 +237,7 @@ fn insert_jumps<'a>(
None => None, None => None,
}, },
Rethrow => None,
Ret(_) => None, Ret(_) => None,
Jump(_, _) => None, Jump(_, _) => None,
RuntimeError(_) => None, RuntimeError(_) => None,

View file

@ -58,9 +58,10 @@ mod test_mono {
arena, arena,
filename, filename,
&module_src, &module_src,
stdlib, &stdlib,
src_dir, src_dir,
exposed_types, exposed_types,
8,
); );
let mut loaded = loaded.expect("failed to load module"); let mut loaded = loaded.expect("failed to load module");
@ -161,6 +162,45 @@ mod test_mono {
) )
} }
#[test]
fn ir_int_add() {
compiles_to_ir(
r#"
x = [ 1,2 ]
5 + 4 + 3 + List.len x
"#,
indoc!(
r#"
procedure List.7 (#Attr.2):
let Test.6 = lowlevel ListLen #Attr.2;
ret Test.6;
procedure Num.24 (#Attr.2, #Attr.3):
let Test.5 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Test.5;
procedure Test.0 ():
let Test.11 = 1i64;
let Test.12 = 2i64;
let Test.1 = Array [Test.11, Test.12];
let Test.9 = 5i64;
let Test.10 = 4i64;
invoke Test.7 = CallByName Num.24 Test.9 Test.10 catch
dec Test.1;
unreachable;
let Test.8 = 3i64;
invoke Test.3 = CallByName Num.24 Test.7 Test.8 catch
dec Test.1;
unreachable;
let Test.4 = CallByName List.7 Test.1;
dec Test.1;
let Test.2 = CallByName Num.24 Test.3 Test.4;
ret Test.2;
"#
),
)
}
#[test] #[test]
fn ir_assignment() { fn ir_assignment() {
compiles_to_ir( compiles_to_ir(
@ -1853,6 +1893,7 @@ mod test_mono {
let Test.2 = S Test.9 Test.8; let Test.2 = S Test.9 Test.8;
let Test.5 = 1i64; let Test.5 = 1i64;
let Test.6 = Index 0 Test.2; let Test.6 = Index 0 Test.2;
dec Test.2;
let Test.7 = lowlevel Eq Test.5 Test.6; let Test.7 = lowlevel Eq Test.5 Test.6;
if Test.7 then if Test.7 then
let Test.3 = 0i64; let Test.3 = 0i64;
@ -1904,12 +1945,16 @@ mod test_mono {
let Test.10 = lowlevel Eq Test.8 Test.9; let Test.10 = lowlevel Eq Test.8 Test.9;
if Test.10 then if Test.10 then
let Test.4 = Index 1 Test.2; let Test.4 = Index 1 Test.2;
inc Test.4;
dec Test.2;
let Test.3 = 1i64; let Test.3 = 1i64;
ret Test.3; ret Test.3;
else else
dec Test.2;
let Test.5 = 0i64; let Test.5 = 0i64;
ret Test.5; ret Test.5;
else else
dec Test.2;
let Test.6 = 0i64; let Test.6 = 0i64;
ret Test.6; ret Test.6;
"# "#
@ -2104,7 +2149,7 @@ mod test_mono {
r#" r#"
app "test" provides [ main ] to "./platform" app "test" provides [ main ] to "./platform"
swap : I64, I64, List a -> List a swap : Nat, Nat, List a -> List a
swap = \i, j, list -> swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) -> Pair (Ok atI) (Ok atJ) ->

View file

@ -1624,8 +1624,8 @@ fn to_diff<'b>(
let right = to_doc(alloc, Parens::Unnecessary, type2); let right = to_doc(alloc, Parens::Unnecessary, type2);
let is_int = |t: &ErrorType| match t { let is_int = |t: &ErrorType| match t {
ErrorType::Type(Symbol::NUM_I64, _) => true, ErrorType::Type(Symbol::NUM_INT, _) => true,
ErrorType::Alias(Symbol::NUM_I64, _, _) => true, ErrorType::Alias(Symbol::NUM_INT, _, _) => true,
ErrorType::Type(Symbol::NUM_NUM, args) => { ErrorType::Type(Symbol::NUM_NUM, args) => {
matches!( &args.get(0) ,Some(ErrorType::Type(Symbol::NUM_INTEGER, _)) | Some(ErrorType::Alias(Symbol::NUM_INTEGER, _, _))) matches!( &args.get(0) ,Some(ErrorType::Type(Symbol::NUM_INTEGER, _)) | Some(ErrorType::Alias(Symbol::NUM_INTEGER, _, _)))
@ -1636,8 +1636,8 @@ fn to_diff<'b>(
_ => false, _ => false,
}; };
let is_float = |t: &ErrorType| match t { let is_float = |t: &ErrorType| match t {
ErrorType::Type(Symbol::NUM_F64, _) => true, ErrorType::Type(Symbol::NUM_FLOAT, _) => true,
ErrorType::Alias(Symbol::NUM_F64, _, _) => true, ErrorType::Alias(Symbol::NUM_FLOAT, _, _) => true,
ErrorType::Type(Symbol::NUM_NUM, args) => { ErrorType::Type(Symbol::NUM_NUM, args) => {
matches!(&args.get(0), Some(ErrorType::Type(Symbol::NUM_FLOATINGPOINT, _)) | Some(ErrorType::Alias(Symbol::NUM_FLOATINGPOINT, _, _))) matches!(&args.get(0), Some(ErrorType::Type(Symbol::NUM_FLOATINGPOINT, _)) | Some(ErrorType::Alias(Symbol::NUM_FLOATINGPOINT, _, _)))
@ -1891,7 +1891,7 @@ fn diff_tag_union<'b>(
alloc.tag_name(field.clone()), alloc.tag_name(field.clone()),
// TODO add spaces between args // TODO add spaces between args
args.iter() args.iter()
.map(|arg| to_doc(alloc, Parens::Unnecessary, arg.clone())) .map(|arg| to_doc(alloc, Parens::InTypeParam, arg.clone()))
.collect(), .collect(),
) )
}; };

View file

@ -94,6 +94,7 @@ mod test_reporting {
problems: &mut mono_problems, problems: &mut mono_problems,
home, home,
ident_ids: &mut ident_ids, ident_ids: &mut ident_ids,
ptr_bytes: 8,
}; };
let _mono_expr = Stmt::new( let _mono_expr = Stmt::new(
&mut mono_env, &mut mono_env,
@ -446,9 +447,9 @@ mod test_reporting {
these names seem close though: these names seem close though:
baz baz
Nat
Str Str
U8 U8
F64
"# "#
), ),
) )
@ -960,7 +961,7 @@ mod test_reporting {
r#" r#"
bar = { bar : 0x3 } bar = { bar : 0x3 }
f : { foo : I64 } -> Bool f : { foo : Int * } -> Bool
f = \_ -> True f = \_ -> True
f bar f bar
@ -977,11 +978,11 @@ mod test_reporting {
This `bar` value is a: This `bar` value is a:
{ bar : I64 } { bar : Int a }
But `f` needs the 1st argument to be: But `f` needs the 1st argument to be:
{ foo : I64 } { foo : Int a }
Tip: Seems like a record field typo. Maybe `bar` should be `foo`? Tip: Seems like a record field typo. Maybe `bar` should be `foo`?
@ -1036,7 +1037,7 @@ mod test_reporting {
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
f : [ Red I64, Green Bool ] -> Bool f : [ Red (Int *), Green Bool ] -> Bool
f = \_ -> True f = \_ -> True
f (Blue 3.14) f (Blue 3.14)
@ -1053,11 +1054,11 @@ mod test_reporting {
This `Blue` global tag application has the type: This `Blue` global tag application has the type:
[ Blue F64 ]a [ Blue (Float a) ]b
But `f` needs the 1st argument to be: But `f` needs the 1st argument to be:
[ Green Bool, Red I64 ] [ Green Bool, Red (Int a) ]
Tip: Seems like a tag typo. Maybe `Blue` should be `Red`? Tip: Seems like a tag typo. Maybe `Blue` should be `Red`?
@ -1074,7 +1075,7 @@ mod test_reporting {
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
x : I64 x : Int *
x = if True then 3.14 else 4 x = if True then 3.14 else 4
x x
@ -1091,11 +1092,11 @@ mod test_reporting {
The 1st branch is a float of type: The 1st branch is a float of type:
F64 Float a
But the type annotation on `x` says it should be: But the type annotation on `x` says it should be:
I64 Int b
Tip: You can convert between Int and Float using functions like Tip: You can convert between Int and Float using functions like
`Num.toFloat` and `Num.round`. `Num.toFloat` and `Num.round`.
@ -1109,7 +1110,7 @@ mod test_reporting {
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
x : I64 x : Int *
x = x =
when True is when True is
_ -> 3.14 _ -> 3.14
@ -1123,18 +1124,18 @@ mod test_reporting {
Something is off with the body of the `x` definition: Something is off with the body of the `x` definition:
1 x : I64 1 x : Int *
2 x = 2 x =
3> when True is 3> when True is
4> _ -> 3.14 4> _ -> 3.14
This `when`expression produces: This `when`expression produces:
F64 Float a
But the type annotation on `x` says it should be: But the type annotation on `x` says it should be:
I64 Int b
Tip: You can convert between Int and Float using functions like Tip: You can convert between Int and Float using functions like
`Num.toFloat` and `Num.round`. `Num.toFloat` and `Num.round`.
@ -1148,7 +1149,7 @@ mod test_reporting {
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
x : I64 -> I64 x : Int * -> Int *
x = \_ -> 3.14 x = \_ -> 3.14
x x
@ -1160,17 +1161,17 @@ mod test_reporting {
Something is off with the body of the `x` definition: Something is off with the body of the `x` definition:
1 x : I64 -> I64 1 x : Int * -> Int *
2 x = \_ -> 3.14 2 x = \_ -> 3.14
^^^^ ^^^^
The body is a float of type: The body is a float of type:
F64 Float a
But the type annotation on `x` says it should be: But the type annotation on `x` says it should be:
I64 Int b
Tip: You can convert between Int and Float using functions like Tip: You can convert between Int and Float using functions like
`Num.toFloat` and `Num.round`. `Num.toFloat` and `Num.round`.
@ -1373,7 +1374,7 @@ mod test_reporting {
Bool Bool
U8 U8
F64 F64
Str Nat
"# "#
), ),
) )
@ -1482,7 +1483,7 @@ mod test_reporting {
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
{ x } : { x : I64 } { x } : { x : Int * }
{ x } = { x: 4.0 } { x } = { x: 4.0 }
x x
@ -1494,17 +1495,17 @@ mod test_reporting {
Something is off with the body of this definition: Something is off with the body of this definition:
1 { x } : { x : I64 } 1 { x } : { x : Int * }
2 { x } = { x: 4.0 } 2 { x } = { x: 4.0 }
^^^^^^^^^^ ^^^^^^^^^^
The body is a record of type: The body is a record of type:
{ x : F64 } { x : Float a }
But the type annotation says it should be: But the type annotation says it should be:
{ x : I64 } { x : Int b }
Tip: You can convert between Int and Float using functions like Tip: You can convert between Int and Float using functions like
`Num.toFloat` and `Num.round`. `Num.toFloat` and `Num.round`.
@ -1643,7 +1644,7 @@ mod test_reporting {
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
x : { a : I64, b : F64, c : Bool } x : { a : Int *, b : Float *, c : Bool }
x = { b: 4.0 } x = { b: 4.0 }
x x
@ -1655,17 +1656,17 @@ mod test_reporting {
Something is off with the body of the `x` definition: Something is off with the body of the `x` definition:
1 x : { a : I64, b : F64, c : Bool } 1 x : { a : Int *, b : Float *, c : Bool }
2 x = { b: 4.0 } 2 x = { b: 4.0 }
^^^^^^^^^^ ^^^^^^^^^^
The body is a record of type: The body is a record of type:
{ b : F64 } { b : Float a }
But the type annotation on `x` says it should be: But the type annotation on `x` says it should be:
{ a : I64, b : F64, c : Bool } { a : Int a, b : Float b, c : Bool }
Tip: Looks like the c and a fields are missing. Tip: Looks like the c and a fields are missing.
"# "#
@ -1787,7 +1788,7 @@ mod test_reporting {
The body is an integer of type: The body is an integer of type:
I64 Int a
But the type annotation on `f` says it should be: But the type annotation on `f` says it should be:
@ -1795,7 +1796,7 @@ mod test_reporting {
Tip: The type annotation uses the type variable `msg` to say that this Tip: The type annotation uses the type variable `msg` to say that this
definition can produce any type of value. But in the body I see that definition can produce any type of value. But in the body I see that
it will only produce a `I64` value of a single specific type. Maybe it will only produce a `Int` value of a single specific type. Maybe
change the type annotation to be more specific? Maybe change the code change the type annotation to be more specific? Maybe change the code
to be more general? to be more general?
"# "#
@ -2093,7 +2094,7 @@ mod test_reporting {
But `add` needs the 2nd argument to be: But `add` needs the 2nd argument to be:
Num (Integer Signed64) Num (Integer a)
"# "#
), ),
) )
@ -2118,11 +2119,11 @@ mod test_reporting {
This argument is a float of type: This argument is a float of type:
F64 Float a
But `add` needs the 2nd argument to be: But `add` needs the 2nd argument to be:
Num (Integer Signed64) Num (Integer a)
Tip: You can convert between Int and Float using functions like Tip: You can convert between Int and Float using functions like
`Num.toFloat` and `Num.round`. `Num.toFloat` and `Num.round`.
@ -2580,9 +2581,9 @@ mod test_reporting {
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
Foo : { x : I64 } Foo : { x : Int * }
f : Foo -> I64 f : Foo -> Int *
f = \r -> r.x f = \r -> r.x
f { y: 3.14 } f { y: 3.14 }
@ -2600,11 +2601,11 @@ mod test_reporting {
This argument is a record of type: This argument is a record of type:
{ y : F64 } { y : Float a }
But `f` needs the 1st argument to be: But `f` needs the 1st argument to be:
{ x : I64 } { x : Int a }
Tip: Seems like a record field typo. Maybe `y` should be `x`? Tip: Seems like a record field typo. Maybe `y` should be `x`?

View file

@ -59,9 +59,10 @@ mod solve_expr {
let result = roc_load::file::load_and_typecheck( let result = roc_load::file::load_and_typecheck(
arena, arena,
full_file_path, full_file_path,
stdlib, &stdlib,
dir.path(), dir.path(),
exposed_types, exposed_types,
8,
); );
dir.close()?; dir.close()?;
@ -169,7 +170,7 @@ mod solve_expr {
#[test] #[test]
fn float_literal() { fn float_literal() {
infer_eq("0.5", "F64"); infer_eq("0.5", "Float *");
} }
#[test] #[test]
@ -762,7 +763,7 @@ mod solve_expr {
(\a -> a) 3.14 (\a -> a) 3.14
"# "#
), ),
"F64", "Float *",
); );
} }
@ -1026,7 +1027,7 @@ mod solve_expr {
#[test] #[test]
fn two_field_record() { fn two_field_record() {
infer_eq("{ x: 5, y : 3.14 }", "{ x : Num *, y : F64 }"); infer_eq("{ x: 5, y : 3.14 }", "{ x : Num *, y : Float * }");
} }
#[test] #[test]
@ -2385,7 +2386,7 @@ mod solve_expr {
threePointZero threePointZero
"# "#
), ),
"F64", "Float *",
); );
} }
@ -2982,7 +2983,7 @@ mod solve_expr {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
partition : I64, I64, List I64 -> [ Pair I64 (List I64) ] partition : Nat, Nat, List (Int a) -> [ Pair Nat (List (Int a)) ]
partition = \low, high, initialList -> partition = \low, high, initialList ->
when List.get initialList high is when List.get initialList high is
Ok _ -> Ok _ ->
@ -2994,7 +2995,7 @@ mod solve_expr {
partition partition
"# "#
), ),
"I64, I64, List I64 -> [ Pair I64 (List I64) ]", "Nat, Nat, List (Int a) -> [ Pair Nat (List (Int a)) ]",
); );
} }
@ -3004,7 +3005,7 @@ mod solve_expr {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
swap : I64, I64, List a -> List a swap : Nat, Nat, List a -> List a
swap = \i, j, list -> swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) -> Pair (Ok atI) (Ok atJ) ->
@ -3015,7 +3016,7 @@ mod solve_expr {
_ -> _ ->
list list
partition : I64, I64, List I64 -> [ Pair I64 (List I64) ] partition : Nat, Nat, List (Int a) -> [ Pair Nat (List (Int a)) ]
partition = \low, high, initialList -> partition = \low, high, initialList ->
when List.get initialList high is when List.get initialList high is
Ok pivot -> Ok pivot ->
@ -3043,7 +3044,7 @@ mod solve_expr {
partition partition
"# "#
), ),
"I64, I64, List I64 -> [ Pair I64 (List I64) ]", "Nat, Nat, List (Int a) -> [ Pair Nat (List (Int a)) ]",
); );
}); });
} }
@ -3077,6 +3078,15 @@ mod solve_expr {
), ),
"Result (Num *) [ OutOfBounds ]*", "Result (Num *) [ OutOfBounds ]*",
); );
infer_eq_without_problem(
indoc!(
r#"
List.get
"#
),
"List a, Nat -> Result a [ OutOfBounds ]*",
);
} }
#[test] #[test]
@ -3117,7 +3127,7 @@ mod solve_expr {
Num.toFloat Num.toFloat
"# "#
), ),
"Num * -> F64", "Num * -> Float *",
); );
} }
@ -3129,7 +3139,7 @@ mod solve_expr {
Num.pow Num.pow
"# "#
), ),
"F64, F64 -> F64", "Float a, Float a -> Float a",
); );
} }
@ -3141,7 +3151,7 @@ mod solve_expr {
Num.ceiling Num.ceiling
"# "#
), ),
"F64 -> I64", "Float * -> Int *",
); );
} }
@ -3153,7 +3163,7 @@ mod solve_expr {
Num.floor Num.floor
"# "#
), ),
"F64 -> I64", "Float * -> Int *",
); );
} }
@ -3165,7 +3175,7 @@ mod solve_expr {
Num.powInt Num.powInt
"# "#
), ),
"I64, I64 -> I64", "Int a, Int a -> Int a",
); );
} }
@ -3177,7 +3187,7 @@ mod solve_expr {
Num.atan Num.atan
"# "#
), ),
"F64 -> F64", "Float a -> Float a",
); );
} }
@ -3444,7 +3454,7 @@ mod solve_expr {
negatePoint { x: 1, y: 2.1, z: 0x3 } negatePoint { x: 1, y: 2.1, z: 0x3 }
"# "#
), ),
"{ x : Num a, y : F64, z : I64 }", "{ x : Num a, y : F64, z : Int * }",
); );
} }
@ -4096,7 +4106,7 @@ mod solve_expr {
r#" r#"
app "test" provides [ partitionHelp ] to "./platform" app "test" provides [ partitionHelp ] to "./platform"
swap : I64, I64, List a -> List a swap : Nat, Nat, List a -> List a
swap = \i, j, list -> swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) -> Pair (Ok atI) (Ok atJ) ->
@ -4107,7 +4117,7 @@ mod solve_expr {
_ -> _ ->
[] []
partitionHelp : I64, I64, List (Num a), I64, (Num a) -> [ Pair I64 (List (Num a)) ] partitionHelp : Nat, Nat, List (Num a), Nat, (Num a) -> [ Pair Nat (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot -> partitionHelp = \i, j, list, high, pivot ->
if j < high then if j < high then
when List.get list j is when List.get list j is
@ -4123,7 +4133,7 @@ mod solve_expr {
Pair i list Pair i list
"# "#
), ),
"I64, I64, List (Num a), I64, Num a -> [ Pair I64 (List (Num a)) ]", "Nat, Nat, List (Num a), Nat, Num a -> [ Pair Nat (List (Num a)) ]",
); );
} }

File diff suppressed because it is too large Load diff

View file

@ -28,6 +28,26 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
aliases.insert(symbol, alias); aliases.insert(symbol, alias);
}; };
// Int range : [ @Int range ]
add_alias(
Symbol::NUM_INT,
BuiltinAlias {
region: Region::zero(),
vars: vec![Located::at(Region::zero(), "range".into())],
typ: int_alias_content(flex(TVAR1)),
},
);
// Float range : [ @Float range ]
add_alias(
Symbol::NUM_FLOAT,
BuiltinAlias {
region: Region::zero(),
vars: vec![Located::at(Region::zero(), "range".into())],
typ: float_alias_content(flex(TVAR1)),
},
);
// Num range : [ @Num range ] // Num range : [ @Num range ]
add_alias( add_alias(
Symbol::NUM_NUM, Symbol::NUM_NUM,
@ -48,6 +68,26 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
}, },
); );
// Natural : [ @Natural ]
add_alias(
Symbol::NUM_NATURAL,
BuiltinAlias {
region: Region::zero(),
vars: vec![],
typ: natural_alias_content(),
},
);
// Nat : Int Natural
add_alias(
Symbol::NUM_NAT,
BuiltinAlias {
region: Region::zero(),
vars: Vec::new(),
typ: int_alias_content(natural_type()),
},
);
// Signed128 : [ @Signed128 ] // Signed128 : [ @Signed128 ]
add_alias( add_alias(
Symbol::NUM_SIGNED128, Symbol::NUM_SIGNED128,
@ -58,7 +98,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
}, },
); );
// I128 : Num (Integer Signed128) // I128 : Int Signed128
add_alias( add_alias(
Symbol::NUM_I128, Symbol::NUM_I128,
BuiltinAlias { BuiltinAlias {
@ -68,7 +108,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
}, },
); );
// U128 : Num (Integer Unsigned128) // U128 : Int Unsigned128
add_alias( add_alias(
Symbol::NUM_U128, Symbol::NUM_U128,
BuiltinAlias { BuiltinAlias {
@ -88,7 +128,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
}, },
); );
// I64 : Num (Integer Signed64) // I64 : Int Signed64
add_alias( add_alias(
Symbol::NUM_I64, Symbol::NUM_I64,
BuiltinAlias { BuiltinAlias {
@ -98,7 +138,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
}, },
); );
// U64 : Num (Integer Unsigned64) // U64 : Int Unsigned64
add_alias( add_alias(
Symbol::NUM_U64, Symbol::NUM_U64,
BuiltinAlias { BuiltinAlias {
@ -118,7 +158,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
}, },
); );
// I32 : Num (Integer Signed32) // I32 : Int Signed32
add_alias( add_alias(
Symbol::NUM_I32, Symbol::NUM_I32,
BuiltinAlias { BuiltinAlias {
@ -128,7 +168,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
}, },
); );
// U32 : Num (Integer Unsigned32) // U32 : Int Unsigned32
add_alias( add_alias(
Symbol::NUM_U32, Symbol::NUM_U32,
BuiltinAlias { BuiltinAlias {
@ -148,7 +188,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
}, },
); );
// I16 : Num (Integer Signed16) // I16 : Int Signed16
add_alias( add_alias(
Symbol::NUM_I16, Symbol::NUM_I16,
BuiltinAlias { BuiltinAlias {
@ -158,7 +198,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
}, },
); );
// U16 : Num (Integer Unsigned16) // U16 : Int Unsigned16
add_alias( add_alias(
Symbol::NUM_U16, Symbol::NUM_U16,
BuiltinAlias { BuiltinAlias {
@ -178,7 +218,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
}, },
); );
// I8 : Num (Integer Signed8) // I8 : Int Signed8
add_alias( add_alias(
Symbol::NUM_I8, Symbol::NUM_I8,
BuiltinAlias { BuiltinAlias {
@ -188,7 +228,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
}, },
); );
// U8 : Num (Integer Unsigned8) // U8 : Int Unsigned8
add_alias( add_alias(
Symbol::NUM_U8, Symbol::NUM_U8,
BuiltinAlias { BuiltinAlias {
@ -228,7 +268,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
}, },
); );
// F64 : Num (FloatingPoint Binary64) // F64 : Float Binary64
add_alias( add_alias(
Symbol::NUM_F64, Symbol::NUM_F64,
BuiltinAlias { BuiltinAlias {
@ -238,7 +278,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
}, },
); );
// F32 : Num (FloatingPoint Binary32) // F32 : Float Binary32
add_alias( add_alias(
Symbol::NUM_F32, Symbol::NUM_F32,
BuiltinAlias { BuiltinAlias {
@ -312,27 +352,39 @@ fn floatingpoint_alias_content(range: SolvedType) -> SolvedType {
// FLOAT // FLOAT
#[inline(always)] #[inline(always)]
pub fn float_type() -> SolvedType { pub fn float_type(range: SolvedType) -> SolvedType {
SolvedType::Alias( SolvedType::Alias(
Symbol::NUM_F64, Symbol::NUM_FLOAT,
Vec::new(), vec![("range".into(), range.clone())],
Box::new(float_alias_content(binary64_type())), Box::new(float_alias_content(range)),
) )
} }
#[inline(always)] #[inline(always)]
fn float_alias_content(typ: SolvedType) -> SolvedType { fn float_alias_content(range: SolvedType) -> SolvedType {
num_type(floatingpoint_type(typ)) num_type(floatingpoint_type(range))
}
// Nat
#[inline(always)]
pub fn nat_type() -> SolvedType {
SolvedType::Alias(Symbol::NUM_NAT, vec![], Box::new(nat_alias_content()))
}
#[inline(always)]
fn nat_alias_content() -> SolvedType {
int_alias_content(natural_type())
} }
// INT // INT
#[inline(always)] #[inline(always)]
pub fn int_type() -> SolvedType { pub fn int_type(range: SolvedType) -> SolvedType {
SolvedType::Alias( SolvedType::Alias(
Symbol::NUM_I64, Symbol::NUM_INT,
Vec::new(), vec![("range".into(), range.clone())],
Box::new(int_alias_content(signed64_type())), Box::new(int_alias_content(range)),
) )
} }
@ -385,6 +437,20 @@ fn binary32_alias_content() -> SolvedType {
single_private_tag(Symbol::NUM_AT_BINARY32, vec![]) single_private_tag(Symbol::NUM_AT_BINARY32, vec![])
} }
#[inline(always)]
pub fn natural_type() -> SolvedType {
SolvedType::Alias(
Symbol::NUM_NATURAL,
vec![],
Box::new(natural_alias_content()),
)
}
#[inline(always)]
fn natural_alias_content() -> SolvedType {
single_private_tag(Symbol::NUM_AT_NATURAL, vec![])
}
#[inline(always)] #[inline(always)]
pub fn signed128_type() -> SolvedType { pub fn signed128_type() -> SolvedType {
SolvedType::Alias( SolvedType::Alias(

View file

@ -14,7 +14,7 @@ use roc_types::types::Type::{self, *};
pub fn int_literal(num_var: Variable, expected: Expected<Type>, region: Region) -> Constraint { pub fn int_literal(num_var: Variable, expected: Expected<Type>, region: Region) -> Constraint {
let num_type = Variable(num_var); let num_type = Variable(num_var);
let reason = Reason::IntLiteral; let reason = Reason::IntLiteral;
let int_type = builtin_type(Symbol::NUM_I64, vec![]); let int_type = builtin_type(Symbol::NUM_INT, vec![]);
let expected_literal = ForReason(reason, int_type, region); let expected_literal = ForReason(reason, int_type, region);
exists( exists(
@ -30,7 +30,7 @@ pub fn int_literal(num_var: Variable, expected: Expected<Type>, region: Region)
pub fn float_literal(num_var: Variable, expected: Expected<Type>, region: Region) -> Constraint { pub fn float_literal(num_var: Variable, expected: Expected<Type>, region: Region) -> Constraint {
let num_type = Variable(num_var); let num_type = Variable(num_var);
let reason = Reason::FloatLiteral; let reason = Reason::FloatLiteral;
let float_type = builtin_type(Symbol::NUM_F64, vec![]); let float_type = builtin_type(Symbol::NUM_FLOAT, vec![]);
let expected_literal = ForReason(reason, float_type, region); let expected_literal = ForReason(reason, float_type, region);
exists( exists(

View file

@ -752,9 +752,9 @@ fn annotate_usage_pattern(pattern: &Pattern, usage: &mut VarUsage) {
match pattern { match pattern {
Identifier(_) Identifier(_)
| IntLiteral(_) | IntLiteral(_, _)
| NumLiteral(_, _) | NumLiteral(_, _)
| FloatLiteral(_) | FloatLiteral(_, _)
| StrLiteral(_) | StrLiteral(_)
| Underscore | Underscore
| Shadowed(_, _) | Shadowed(_, _)
@ -785,8 +785,8 @@ pub fn annotate_usage(expr: &Expr, usage: &mut VarUsage) {
match expr { match expr {
RuntimeError(_) RuntimeError(_)
| Num(_, _) | Num(_, _)
| Int(_, _) | Int(_, _, _)
| Float(_, _) | Float(_, _, _)
| Str { .. } | Str { .. }
| EmptyRecord | EmptyRecord
| Accessor { .. } | Accessor { .. }

View file

@ -129,9 +129,10 @@ fn files_to_documentations(
let mut loaded = roc_load::file::load_and_typecheck( let mut loaded = roc_load::file::load_and_typecheck(
&arena, &arena,
filename, filename,
std_lib.clone(), &std_lib,
src_dir, src_dir,
MutMap::default(), MutMap::default(),
8, // TODO: Is it okay to hardcode ptr_bytes here? I think it should be fine since we'er only type checking (also, 8 => 32bit system)
) )
.expect("TODO gracefully handle load failing"); .expect("TODO gracefully handle load failing");
files_docs.extend(loaded.documentation.drain().map(|x| x.1)); files_docs.extend(loaded.documentation.drain().map(|x| x.1));

View file

@ -66,6 +66,8 @@ cgmath = "0.17.0"
itertools = "0.9.0" itertools = "0.9.0"
snafu = { version = "0.6", features = ["backtraces"] } snafu = { version = "0.6", features = ["backtraces"] }
colored = "2" colored = "2"
pest = "2.1"
pest_derive = "2.1"
[dependencies.bytemuck] [dependencies.bytemuck]

View file

@ -55,10 +55,11 @@ These are potentially inspirational resources for the editor's design.
### Productivity features ### Productivity features
* When refactoring; * When refactoring;
- cutting and pasting code to a new file should automatically add imports to the new file and delete them from the old file. - Cutting and pasting code to a new file should automatically add imports to the new file and delete them from the old file.
- When renaming a function for example, references in comments should be brought to the user's attention. - Ability to link e.g. variable name in comments to actual variable name. Comment is automatically updated when variable name is changed.
* Automatically create all "arms" when pattern matching after entering `when var is` based on the type. * Automatically create all "arms" when pattern matching after entering `when var is` based on the type.
- All `when ... is` should be updated if the type is changed, e.g. adding Indigo to the Color type should add an arm everywhere where `when color is` is used. - All `when ... is` should be updated if the type is changed, e.g. adding Indigo to the Color type should add an arm everywhere where `when color is` is used.
* When a function is called like `foo(false)`, the name of the boolean argument should be shown automatically; `foo(`*is_active:*`false)`. This should be done for booleans and numbers.
### Non-Code Related Inspiration ### Non-Code Related Inspiration

View file

@ -24,6 +24,8 @@ pub enum EdError {
err_msg: String, err_msg: String,
backtrace: Backtrace, backtrace: Backtrace,
}, },
#[snafu(display("MissingGlyphDims: glyph_dim_rect_opt was None for model. It needs to be set using the example_code_glyph_rect function."))]
MissingGlyphDims {},
} }
pub type EdResult<T, E = EdError> = std::result::Result<T, E>; pub type EdResult<T, E = EdError> = std::result::Result<T, E>;

View file

@ -1 +1,5 @@
pub const WHITE: [f32; 3] = [1.0, 1.0, 1.0]; pub const WHITE: [f32; 3] = [1.0, 1.0, 1.0];
pub const TXT_COLOR: [f32; 4] = [0.4666, 0.2, 1.0, 1.0];
pub const CODE_COLOR: [f32; 4] = [0.0, 0.05, 0.46, 1.0];
pub const CARET_COLOR: [f32; 3] = WHITE;
pub const SELECT_COLOR: [f32; 3] = [0.45, 0.61, 1.0];

View file

@ -1,6 +1,6 @@
// Adapted from https://github.com/sotrh/learn-wgpu // Adapted from https://github.com/sotrh/learn-wgpu
// by Benjamin Hansen, licensed under the MIT license // by Benjamin Hansen, licensed under the MIT license
use crate::graphics::lowlevel::vertex::Vertex; use super::vertex::Vertex;
use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::rect::Rect;
use bumpalo::collections::Vec as BumpVec; use bumpalo::collections::Vec as BumpVec;
use wgpu::util::{BufferInitDescriptor, DeviceExt}; use wgpu::util::{BufferInitDescriptor, DeviceExt};

View file

@ -1,3 +1,4 @@
pub mod buffer; pub mod buffer;
pub mod ortho; pub mod ortho;
pub mod pipelines;
pub mod vertex; pub mod vertex;

View file

@ -66,6 +66,7 @@ pub fn update_ortho_buffer(
cmd_queue.submit(Some(encoder.finish())); cmd_queue.submit(Some(encoder.finish()));
} }
#[derive(Debug)]
pub struct OrthoResources { pub struct OrthoResources {
pub buffer: Buffer, pub buffer: Buffer,
pub bind_group_layout: BindGroupLayout, pub bind_group_layout: BindGroupLayout,

View file

@ -0,0 +1,71 @@
use super::ortho::{init_ortho, OrthoResources};
use super::vertex::Vertex;
pub struct RectResources {
pub pipeline: wgpu::RenderPipeline,
pub ortho: OrthoResources,
}
pub fn make_rect_pipeline(
gpu_device: &wgpu::Device,
swap_chain_descr: &wgpu::SwapChainDescriptor,
) -> RectResources {
let ortho = init_ortho(swap_chain_descr.width, swap_chain_descr.height, gpu_device);
let pipeline_layout = gpu_device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
bind_group_layouts: &[&ortho.bind_group_layout],
push_constant_ranges: &[],
label: Some("Rectangle pipeline layout"),
});
let pipeline = create_render_pipeline(
&gpu_device,
&pipeline_layout,
swap_chain_descr.format,
&[Vertex::DESC],
wgpu::include_spirv!("../shaders/rect.vert.spv"),
wgpu::include_spirv!("../shaders/rect.frag.spv"),
);
RectResources { pipeline, ortho }
}
pub fn create_render_pipeline(
device: &wgpu::Device,
layout: &wgpu::PipelineLayout,
color_format: wgpu::TextureFormat,
vertex_descs: &[wgpu::VertexBufferDescriptor],
vs_src: wgpu::ShaderModuleSource,
fs_src: wgpu::ShaderModuleSource,
) -> wgpu::RenderPipeline {
let vs_module = device.create_shader_module(vs_src);
let fs_module = device.create_shader_module(fs_src);
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Render pipeline"),
layout: Some(&layout),
vertex_stage: wgpu::ProgrammableStageDescriptor {
module: &vs_module,
entry_point: "main",
},
fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
module: &fs_module,
entry_point: "main",
}),
rasterization_state: None,
primitive_topology: wgpu::PrimitiveTopology::TriangleList,
color_states: &[wgpu::ColorStateDescriptor {
format: color_format,
color_blend: wgpu::BlendDescriptor::REPLACE,
alpha_blend: wgpu::BlendDescriptor::REPLACE,
write_mask: wgpu::ColorWrite::ALL,
}],
depth_stencil_state: None,
sample_count: 1,
sample_mask: !0,
alpha_to_coverage_enabled: false,
vertex_state: wgpu::VertexStateDescriptor {
index_format: wgpu::IndexFormat::Uint32,
vertex_buffers: vertex_descs,
},
})
}

View file

@ -1,3 +1,4 @@
pub mod colors; pub mod colors;
pub mod lowlevel; pub mod lowlevel;
pub mod primitives; pub mod primitives;
pub mod style;

View file

@ -1,10 +1,11 @@
// Adapted from https://github.com/sotrh/learn-wgpu // Adapted from https://github.com/sotrh/learn-wgpu
// by Benjamin Hansen, licensed under the MIT license // by Benjamin Hansen, licensed under the MIT license
use crate::graphics::primitives::rect::Rect; use super::rect::Rect;
use crate::graphics::colors::CODE_COLOR;
use crate::graphics::style::{CODE_FONT_SIZE, CODE_TXT_XY};
use ab_glyph::{FontArc, Glyph, InvalidFont}; use ab_glyph::{FontArc, Glyph, InvalidFont};
use cgmath::{Vector2, Vector4}; use cgmath::{Vector2, Vector4};
use itertools::Itertools;
use wgpu_glyph::{ab_glyph, GlyphBrush, GlyphBrushBuilder, GlyphCruncher, Section}; use wgpu_glyph::{ab_glyph, GlyphBrush, GlyphBrushBuilder, GlyphCruncher, Section};
#[derive(Debug)] #[derive(Debug)]
@ -25,22 +26,50 @@ impl Default for Text {
area_bounds: (std::f32::INFINITY, std::f32::INFINITY).into(), area_bounds: (std::f32::INFINITY, std::f32::INFINITY).into(),
color: (1.0, 1.0, 1.0, 1.0).into(), color: (1.0, 1.0, 1.0, 1.0).into(),
text: String::new(), text: String::new(),
size: 16.0, size: CODE_FONT_SIZE,
visible: true, visible: true,
centered: false, centered: false,
} }
} }
} }
// returns bounding boxes for every glyph // necessary to get dimensions for caret
pub fn queue_text_draw(text: &Text, glyph_brush: &mut GlyphBrush<()>) -> Vec<Vec<Rect>> { pub fn example_code_glyph_rect(glyph_brush: &mut GlyphBrush<()>) -> Rect {
let layout = wgpu_glyph::Layout::default().h_align(if text.centered { let code_text = Text {
position: CODE_TXT_XY.into(),
area_bounds: (std::f32::INFINITY, std::f32::INFINITY).into(),
color: CODE_COLOR.into(),
text: "a".to_owned(),
size: CODE_FONT_SIZE,
..Default::default()
};
let layout = layout_from_text(&code_text);
let section = section_from_text(&code_text, layout);
let mut glyph_section_iter = glyph_brush.glyphs_custom_layout(section, &layout);
if let Some(glyph) = glyph_section_iter.next() {
glyph_to_rect(glyph)
} else {
unreachable!();
}
}
fn layout_from_text(text: &Text) -> wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker> {
wgpu_glyph::Layout::default().h_align(if text.centered {
wgpu_glyph::HorizontalAlign::Center wgpu_glyph::HorizontalAlign::Center
} else { } else {
wgpu_glyph::HorizontalAlign::Left wgpu_glyph::HorizontalAlign::Left
}); })
}
let section = Section { fn section_from_text(
text: &Text,
layout: wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker>,
) -> wgpu_glyph::Section {
Section {
screen_position: text.position.into(), screen_position: text.position.into(),
bounds: text.area_bounds.into(), bounds: text.area_bounds.into(),
layout, layout,
@ -50,19 +79,24 @@ pub fn queue_text_draw(text: &Text, glyph_brush: &mut GlyphBrush<()>) -> Vec<Vec
wgpu_glyph::Text::new(&text.text) wgpu_glyph::Text::new(&text.text)
.with_color(text.color) .with_color(text.color)
.with_scale(text.size), .with_scale(text.size),
); )
}
// returns glyphs per line
pub fn queue_text_draw(text: &Text, glyph_brush: &mut GlyphBrush<()>) {
let layout = layout_from_text(text);
let section = section_from_text(text, layout);
glyph_brush.queue(section.clone()); glyph_brush.queue(section.clone());
}
let glyph_section_iter = glyph_brush.glyphs_custom_layout(section, &layout); fn glyph_to_rect(glyph: &wgpu_glyph::SectionGlyph) -> Rect {
let position = glyph.glyph.position;
glyph_section_iter let px_scale = glyph.glyph.scale;
.map(|section_glyph| { let width = glyph_width(&glyph.glyph);
let position = section_glyph.glyph.position;
let px_scale = section_glyph.glyph.scale;
let width = glyph_width(&section_glyph.glyph);
let height = px_scale.y; let height = px_scale.y;
let top_y = glyph_top_y(&section_glyph.glyph); let top_y = glyph_top_y(&glyph.glyph);
Rect { Rect {
top_left_coords: [position.x, top_y].into(), top_left_coords: [position.x, top_y].into(),
@ -70,28 +104,6 @@ pub fn queue_text_draw(text: &Text, glyph_brush: &mut GlyphBrush<()>) -> Vec<Vec
height, height,
color: [1.0, 1.0, 1.0], color: [1.0, 1.0, 1.0],
} }
})
.group_by(|rect| rect.top_left_coords.y)
.into_iter()
.map(|(_y_coord, rect_group)| {
let mut rects_vec = rect_group.collect::<Vec<Rect>>();
let last_rect_opt = rects_vec.last().cloned();
// add extra rect to make it easy to highlight the newline character
if let Some(last_rect) = last_rect_opt {
rects_vec.push(Rect {
top_left_coords: [
last_rect.top_left_coords.x + last_rect.width,
last_rect.top_left_coords.y,
]
.into(),
width: last_rect.width,
height: last_rect.height,
color: last_rect.color,
});
}
rects_vec
})
.collect()
} }
pub fn glyph_top_y(glyph: &Glyph) -> f32 { pub fn glyph_top_y(glyph: &Glyph) -> f32 {
@ -101,7 +113,7 @@ pub fn glyph_top_y(glyph: &Glyph) -> f32 {
} }
pub fn glyph_width(glyph: &Glyph) -> f32 { pub fn glyph_width(glyph: &Glyph) -> f32 {
glyph.scale.x * 0.5 glyph.scale.x * 0.4765
} }
pub fn build_glyph_brush( pub fn build_glyph_brush(

View file

@ -0,0 +1,2 @@
pub const CODE_FONT_SIZE: f32 = 40.0;
pub const CODE_TXT_XY: (f32, f32) = (30.0, 90.0);

View file

@ -1,4 +1,4 @@
use crate::tea::model::Model; use crate::tea::ed_model::EdModel;
use crate::tea::update::{move_caret_down, move_caret_left, move_caret_right, move_caret_up}; use crate::tea::update::{move_caret_down, move_caret_left, move_caret_right, move_caret_up};
use winit::event::{ElementState, ModifiersState, VirtualKeyCode}; use winit::event::{ElementState, ModifiersState, VirtualKeyCode};
@ -6,7 +6,7 @@ pub fn handle_keydown(
elem_state: ElementState, elem_state: ElementState,
virtual_keycode: VirtualKeyCode, virtual_keycode: VirtualKeyCode,
modifiers: ModifiersState, modifiers: ModifiersState,
model: &mut Model, model: &mut EdModel,
) { ) {
use winit::event::VirtualKeyCode::*; use winit::event::VirtualKeyCode::*;

View file

@ -8,21 +8,37 @@
// See this link to learn wgpu: https://sotrh.github.io/learn-wgpu/ // See this link to learn wgpu: https://sotrh.github.io/learn-wgpu/
use crate::error::print_err; extern crate pest;
#[cfg(test)]
#[macro_use]
extern crate pest_derive;
use crate::error::EdError::MissingGlyphDims;
use crate::error::{print_err, EdResult};
use crate::graphics::colors::{CARET_COLOR, CODE_COLOR, TXT_COLOR};
use crate::graphics::lowlevel::buffer::create_rect_buffers; use crate::graphics::lowlevel::buffer::create_rect_buffers;
use crate::graphics::lowlevel::ortho::{init_ortho, update_ortho_buffer, OrthoResources}; use crate::graphics::lowlevel::ortho::update_ortho_buffer;
use crate::graphics::lowlevel::vertex::Vertex; use crate::graphics::lowlevel::pipelines;
use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::rect::Rect;
use crate::graphics::primitives::text::{build_glyph_brush, queue_text_draw, Text}; use crate::graphics::primitives::text::{
build_glyph_brush, example_code_glyph_rect, queue_text_draw, Text,
};
use crate::graphics::style::CODE_FONT_SIZE;
use crate::graphics::style::CODE_TXT_XY;
use crate::selection::create_selection_rects; use crate::selection::create_selection_rects;
use crate::tea::{model, update}; use crate::tea::ed_model::EdModel;
use crate::util::is_newline; use crate::tea::{ed_model, update};
use bumpalo::collections::Vec as BumpVec;
use bumpalo::Bump; use bumpalo::Bump;
use model::Position; use cgmath::Vector2;
use ed_model::Position;
use pipelines::RectResources;
use std::error::Error; use std::error::Error;
use std::io; use std::io;
use std::path::Path; use std::path::Path;
use wgpu::{CommandEncoder, RenderPass, TextureView}; use wgpu::{CommandEncoder, RenderPass, TextureView};
use wgpu_glyph::GlyphBrush;
use winit::dpi::PhysicalSize;
use winit::event; use winit::event;
use winit::event::{Event, ModifiersState}; use winit::event::{Event, ModifiersState};
use winit::event_loop::ControlFlow; use winit::event_loop::ControlFlow;
@ -103,12 +119,13 @@ fn run_event_loop() -> Result<(), Box<dyn Error>> {
let mut swap_chain = gpu_device.create_swap_chain(&surface, &swap_chain_descr); let mut swap_chain = gpu_device.create_swap_chain(&surface, &swap_chain_descr);
let (rect_pipeline, ortho) = make_rect_pipeline(&gpu_device, &swap_chain_descr); let rect_resources = pipelines::make_rect_pipeline(&gpu_device, &swap_chain_descr);
let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?; let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?;
let is_animating = true; let is_animating = true;
let mut ed_model = model::init_model(); let mut ed_model = ed_model::init_model();
ed_model.glyph_dim_rect_opt = Some(example_code_glyph_rect(&mut glyph_brush));
let mut keyboard_modifiers = ModifiersState::empty(); let mut keyboard_modifiers = ModifiersState::empty();
let arena = Bump::new(); let arena = Bump::new();
@ -154,7 +171,7 @@ fn run_event_loop() -> Result<(), Box<dyn Error>> {
size.width, size.width,
size.height, size.height,
&gpu_device, &gpu_device,
&ortho.buffer, &rect_resources.ortho.buffer,
&cmd_queue, &cmd_queue,
); );
} }
@ -163,7 +180,7 @@ fn run_event_loop() -> Result<(), Box<dyn Error>> {
event: event::WindowEvent::ReceivedCharacter(ch), event: event::WindowEvent::ReceivedCharacter(ch),
.. ..
} => { } => {
update_text_state(&mut ed_model, &ch); update::update_text_state(&mut ed_model, &ch);
} }
//Keyboard Input //Keyboard Input
Event::WindowEvent { Event::WindowEvent {
@ -199,40 +216,29 @@ fn run_event_loop() -> Result<(), Box<dyn Error>> {
.expect("Failed to acquire next SwapChainFrame") .expect("Failed to acquire next SwapChainFrame")
.output; .output;
let glyph_bounds_rects = queue_all_text(
queue_all_text(&size, &ed_model.lines, ed_model.caret_pos, &mut glyph_brush); &size,
&ed_model.lines,
if let Some(selection) = ed_model.selection_opt { ed_model.caret_pos,
let selection_rects_res = CODE_TXT_XY.into(),
create_selection_rects(selection, &glyph_bounds_rects, &arena); &mut glyph_brush,
match selection_rects_res {
Ok(selection_rects) => {
if !selection_rects.is_empty() {
let rect_buffers = create_rect_buffers(
&gpu_device,
&mut encoder,
&selection_rects,
); );
let mut render_pass = begin_render_pass(&mut encoder, &frame.view); match draw_all_rects(
&ed_model,
render_pass.set_pipeline(&rect_pipeline); &ed_model.glyph_dim_rect_opt,
render_pass.set_bind_group(0, &ortho.bind_group, &[]); &arena,
render_pass &mut encoder,
.set_vertex_buffer(0, rect_buffers.vertex_buffer.slice(..)); &frame.view,
render_pass.set_index_buffer(rect_buffers.index_buffer.slice(..)); &gpu_device,
render_pass.draw_indexed(0..rect_buffers.num_rects, 0, 0..1); &rect_resources,
} ) {
} Ok(()) => (),
Err(e) => { Err(e) => {
print_err(&e);
begin_render_pass(&mut encoder, &frame.view); begin_render_pass(&mut encoder, &frame.view);
print_err(&e) //TODO draw error text on screen
} }
} }
} else {
begin_render_pass(&mut encoder, &frame.view);
}
// draw all text // draw all text
glyph_brush glyph_brush
@ -265,6 +271,45 @@ fn run_event_loop() -> Result<(), Box<dyn Error>> {
}) })
} }
fn draw_all_rects(
ed_model: &EdModel,
glyph_dim_rect_opt: &Option<Rect>,
arena: &Bump,
encoder: &mut CommandEncoder,
texture_view: &TextureView,
gpu_device: &wgpu::Device,
rect_resources: &RectResources,
) -> EdResult<()> {
let mut all_rects: BumpVec<Rect> = BumpVec::new_in(arena);
let glyph_rect = if let Some(glyph_dim_rect) = glyph_dim_rect_opt {
glyph_dim_rect
} else {
return Err(MissingGlyphDims {});
};
if let Some(selection) = ed_model.selection_opt {
let mut selection_rects =
create_selection_rects(selection, &ed_model.lines, glyph_rect, &arena)?;
all_rects.append(&mut selection_rects);
}
all_rects.push(make_caret_rect(ed_model.caret_pos, glyph_rect)?);
let rect_buffers = create_rect_buffers(gpu_device, encoder, &all_rects);
let mut render_pass = begin_render_pass(encoder, texture_view);
render_pass.set_pipeline(&rect_resources.pipeline);
render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]);
render_pass.set_vertex_buffer(0, rect_buffers.vertex_buffer.slice(..));
render_pass.set_index_buffer(rect_buffers.index_buffer.slice(..));
render_pass.draw_indexed(0..rect_buffers.num_rects, 0, 0..1);
Ok(())
}
fn begin_render_pass<'a>( fn begin_render_pass<'a>(
encoder: &'a mut CommandEncoder, encoder: &'a mut CommandEncoder,
texture_view: &'a TextureView, texture_view: &'a TextureView,
@ -287,101 +332,53 @@ fn begin_render_pass<'a>(
}) })
} }
fn make_rect_pipeline( fn make_caret_rect(caret_pos: Position, glyph_dim_rect: &Rect) -> EdResult<Rect> {
gpu_device: &wgpu::Device, let caret_y =
swap_chain_descr: &wgpu::SwapChainDescriptor, glyph_dim_rect.top_left_coords.y + (caret_pos.line as f32) * glyph_dim_rect.height;
) -> (wgpu::RenderPipeline, OrthoResources) {
let ortho = init_ortho(swap_chain_descr.width, swap_chain_descr.height, gpu_device);
let pipeline_layout = gpu_device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let caret_x =
bind_group_layouts: &[&ortho.bind_group_layout], glyph_dim_rect.top_left_coords.x + glyph_dim_rect.width * (caret_pos.column as f32);
push_constant_ranges: &[],
label: Some("Rectangle pipeline layout"),
});
let pipeline = create_render_pipeline(
&gpu_device,
&pipeline_layout,
swap_chain_descr.format,
&[Vertex::DESC],
wgpu::include_spirv!("graphics/shaders/rect.vert.spv"),
wgpu::include_spirv!("graphics/shaders/rect.frag.spv"),
);
(pipeline, ortho) Ok(Rect {
} top_left_coords: (caret_x, caret_y).into(),
height: glyph_dim_rect.height,
fn create_render_pipeline( width: 2.0,
device: &wgpu::Device, color: CARET_COLOR,
layout: &wgpu::PipelineLayout,
color_format: wgpu::TextureFormat,
vertex_descs: &[wgpu::VertexBufferDescriptor],
vs_src: wgpu::ShaderModuleSource,
fs_src: wgpu::ShaderModuleSource,
) -> wgpu::RenderPipeline {
let vs_module = device.create_shader_module(vs_src);
let fs_module = device.create_shader_module(fs_src);
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Render pipeline"),
layout: Some(&layout),
vertex_stage: wgpu::ProgrammableStageDescriptor {
module: &vs_module,
entry_point: "main",
},
fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
module: &fs_module,
entry_point: "main",
}),
rasterization_state: None,
primitive_topology: wgpu::PrimitiveTopology::TriangleList,
color_states: &[wgpu::ColorStateDescriptor {
format: color_format,
color_blend: wgpu::BlendDescriptor::REPLACE,
alpha_blend: wgpu::BlendDescriptor::REPLACE,
write_mask: wgpu::ColorWrite::ALL,
}],
depth_stencil_state: None,
sample_count: 1,
sample_mask: !0,
alpha_to_coverage_enabled: false,
vertex_state: wgpu::VertexStateDescriptor {
index_format: wgpu::IndexFormat::Uint32,
vertex_buffers: vertex_descs,
},
}) })
} }
// returns bounding boxes for every glyph // returns bounding boxes for every glyph
fn queue_all_text( fn queue_all_text(
size: &winit::dpi::PhysicalSize<u32>, size: &PhysicalSize<u32>,
lines: &[String], lines: &[String],
caret_pos: Position, caret_pos: Position,
glyph_brush: &mut wgpu_glyph::GlyphBrush<()>, code_coords: Vector2<f32>,
) -> Vec<Vec<Rect>> { glyph_brush: &mut GlyphBrush<()>,
) {
let area_bounds = (size.width as f32, size.height as f32).into(); let area_bounds = (size.width as f32, size.height as f32).into();
let main_label = Text { let main_label = Text {
position: (30.0, 30.0).into(), position: (30.0, 30.0).into(),
area_bounds, area_bounds,
color: (0.4666, 0.2, 1.0, 1.0).into(), color: TXT_COLOR.into(),
text: String::from("Enter some text:"), text: String::from("Enter some text:"),
size: 40.0, size: CODE_FONT_SIZE,
..Default::default() ..Default::default()
}; };
let code_text = Text { let code_text = Text {
position: (30.0, 90.0).into(), position: code_coords,
area_bounds, area_bounds,
color: (0.0, 0.05, 0.46, 1.0).into(), color: CODE_COLOR.into(),
text: lines.join(""), text: lines.join(""),
size: 40.0, size: CODE_FONT_SIZE,
..Default::default() ..Default::default()
}; };
let caret_pos_label = Text { let caret_pos_label = Text {
position: (30.0, 530.0).into(), position: (30.0, (size.height as f32) - 45.0).into(),
area_bounds, area_bounds,
color: (0.4666, 0.2, 1.0, 1.0).into(), color: TXT_COLOR.into(),
text: format!("Ln {}, Col {}", caret_pos.line, caret_pos.column), text: format!("Ln {}, Col {}", caret_pos.line, caret_pos.column),
size: 30.0, size: 30.0,
..Default::default() ..Default::default()
@ -391,55 +388,5 @@ fn queue_all_text(
queue_text_draw(&caret_pos_label, glyph_brush); queue_text_draw(&caret_pos_label, glyph_brush);
queue_text_draw(&code_text, glyph_brush) queue_text_draw(&code_text, glyph_brush);
}
fn update_text_state(ed_model: &mut model::Model, received_char: &char) {
ed_model.selection_opt = None;
match received_char {
'\u{8}' | '\u{7f}' => {
// In Linux, we get a '\u{8}' when you press backspace,
// but in macOS we get '\u{7f}'.
if let Some(last_line) = ed_model.lines.last_mut() {
if !last_line.is_empty() {
last_line.pop();
} else if ed_model.lines.len() > 1 {
ed_model.lines.pop();
}
ed_model.caret_pos =
update::move_caret_left(ed_model.caret_pos, None, false, &ed_model.lines).0;
}
}
'\u{e000}'..='\u{f8ff}' | '\u{f0000}'..='\u{ffffd}' | '\u{100000}'..='\u{10fffd}' => {
// These are private use characters; ignore them.
// See http://www.unicode.org/faq/private_use.html
}
ch if is_newline(ch) => {
if let Some(last_line) = ed_model.lines.last_mut() {
last_line.push(*received_char)
}
ed_model.lines.push(String::new());
ed_model.caret_pos = Position {
line: ed_model.caret_pos.line + 1,
column: 0,
};
ed_model.selection_opt = None;
}
_ => {
let nr_lines = ed_model.lines.len();
if let Some(last_line) = ed_model.lines.last_mut() {
last_line.push(*received_char);
ed_model.caret_pos = Position {
line: nr_lines - 1,
column: last_line.len(),
};
ed_model.selection_opt = None;
}
}
}
} }

File diff suppressed because it is too large Load diff

View file

@ -1,17 +1,20 @@
use crate::graphics::primitives::rect::Rect;
use std::cmp::Ordering; use std::cmp::Ordering;
#[derive(Debug)] #[derive(Debug)]
pub struct Model { pub struct EdModel {
pub lines: Vec<String>, pub lines: Vec<String>,
pub caret_pos: Position, pub caret_pos: Position,
pub selection_opt: Option<RawSelection>, pub selection_opt: Option<RawSelection>,
pub glyph_dim_rect_opt: Option<Rect>,
} }
pub fn init_model() -> Model { pub fn init_model() -> EdModel {
Model { EdModel {
lines: vec![String::new()], lines: vec![String::new()],
caret_pos: Position { line: 0, column: 0 }, caret_pos: Position { line: 0, column: 0 },
selection_opt: None, selection_opt: None,
glyph_dim_rect_opt: None,
} }
} }

View file

@ -1,2 +1,2 @@
pub mod model; pub mod ed_model;
pub mod update; pub mod update;

View file

@ -1,4 +1,5 @@
use crate::tea::model::{Position, RawSelection}; use super::ed_model::EdModel;
use super::ed_model::{Position, RawSelection};
use crate::util::is_newline; use crate::util::is_newline;
use std::cmp::{max, min}; use std::cmp::{max, min};
@ -11,7 +12,12 @@ pub fn move_caret_left(
let old_line_nr = old_caret_pos.line; let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column; let old_col_nr = old_caret_pos.column;
let (line_nr, col_nr) = if old_col_nr == 0 { let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => (old_selection.start_pos.line, old_selection.start_pos.column),
None => unreachable!(),
}
} else if old_col_nr == 0 {
if old_line_nr == 0 { if old_line_nr == 0 {
(0, 0) (0, 0)
} else if let Some(curr_line) = lines.get(old_line_nr - 1) { } else if let Some(curr_line) = lines.get(old_line_nr - 1) {
@ -30,6 +36,16 @@ pub fn move_caret_left(
let new_selection_opt = if shift_pressed { let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt { if let Some(old_selection) = old_selection_opt {
if old_caret_pos >= old_selection.end_pos {
if new_caret_pos == old_selection.start_pos {
None
} else {
Some(RawSelection {
start_pos: old_selection.start_pos,
end_pos: new_caret_pos,
})
}
} else {
Some(RawSelection { Some(RawSelection {
start_pos: Position { start_pos: Position {
line: line_nr, line: line_nr,
@ -37,6 +53,7 @@ pub fn move_caret_left(
}, },
end_pos: old_selection.end_pos, end_pos: old_selection.end_pos,
}) })
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) { } else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
Some(RawSelection { Some(RawSelection {
start_pos: Position { start_pos: Position {
@ -67,7 +84,12 @@ pub fn move_caret_right(
let old_line_nr = old_caret_pos.line; let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column; let old_col_nr = old_caret_pos.column;
let (line_nr, col_nr) = if let Some(curr_line) = lines.get(old_line_nr) { let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
match old_selection_opt {
Some(old_selection) => (old_selection.end_pos.line, old_selection.end_pos.column),
None => unreachable!(),
}
} else if let Some(curr_line) = lines.get(old_line_nr) {
if let Some(last_char) = curr_line.chars().last() { if let Some(last_char) = curr_line.chars().last() {
if is_newline(&last_char) { if is_newline(&last_char) {
if old_col_nr + 1 > curr_line.len() - 1 { if old_col_nr + 1 > curr_line.len() - 1 {
@ -94,6 +116,16 @@ pub fn move_caret_right(
let new_selection_opt = if shift_pressed { let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt { if let Some(old_selection) = old_selection_opt {
if old_caret_pos <= old_selection.start_pos {
if new_caret_pos == old_selection.end_pos {
None
} else {
Some(RawSelection {
start_pos: new_caret_pos,
end_pos: old_selection.end_pos,
})
}
} else {
Some(RawSelection { Some(RawSelection {
start_pos: old_selection.start_pos, start_pos: old_selection.start_pos,
end_pos: Position { end_pos: Position {
@ -101,6 +133,7 @@ pub fn move_caret_right(
column: col_nr, column: col_nr,
}, },
}) })
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) { } else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
Some(RawSelection { Some(RawSelection {
start_pos: Position { start_pos: Position {
@ -131,10 +164,15 @@ pub fn move_caret_up(
let old_line_nr = old_caret_pos.line; let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column; let old_col_nr = old_caret_pos.column;
let (line_nr, col_nr) = if old_line_nr == 0 { let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
(old_line_nr, old_col_nr) match old_selection_opt {
Some(old_selection) => (old_selection.start_pos.line, old_selection.start_pos.column),
None => unreachable!(),
}
} else if old_line_nr == 0 {
(old_line_nr, 0)
} else if let Some(prev_line) = lines.get(old_line_nr - 1) { } else if let Some(prev_line) = lines.get(old_line_nr - 1) {
if prev_line.len() < old_col_nr { if prev_line.len() <= old_col_nr {
(old_line_nr - 1, prev_line.len() - 1) (old_line_nr - 1, prev_line.len() - 1)
} else { } else {
(old_line_nr - 1, old_col_nr) (old_line_nr - 1, old_col_nr)
@ -150,10 +188,21 @@ pub fn move_caret_up(
let new_selection_opt = if shift_pressed { let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt { if let Some(old_selection) = old_selection_opt {
if old_selection.end_pos <= old_caret_pos {
if new_caret_pos == old_selection.start_pos {
None
} else {
Some(RawSelection {
start_pos: min(old_selection.start_pos, new_caret_pos),
end_pos: max(old_selection.start_pos, new_caret_pos),
})
}
} else {
Some(RawSelection { Some(RawSelection {
start_pos: new_caret_pos, start_pos: new_caret_pos,
end_pos: old_selection.end_pos, end_pos: old_selection.end_pos,
}) })
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) { } else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
Some(RawSelection { Some(RawSelection {
start_pos: min(old_caret_pos, new_caret_pos), start_pos: min(old_caret_pos, new_caret_pos),
@ -178,10 +227,19 @@ pub fn move_caret_down(
let old_line_nr = old_caret_pos.line; let old_line_nr = old_caret_pos.line;
let old_col_nr = old_caret_pos.column; let old_col_nr = old_caret_pos.column;
let (line_nr, col_nr) = if old_line_nr + 1 >= lines.len() { let (line_nr, col_nr) = if old_selection_opt.is_some() && !shift_pressed {
(old_line_nr, old_col_nr) match old_selection_opt {
Some(old_selection) => (old_selection.end_pos.line, old_selection.end_pos.column),
None => unreachable!(),
}
} else if old_line_nr + 1 >= lines.len() {
if let Some(curr_line) = lines.get(old_line_nr) {
(old_line_nr, curr_line.len())
} else {
unreachable!()
}
} else if let Some(next_line) = lines.get(old_line_nr + 1) { } else if let Some(next_line) = lines.get(old_line_nr + 1) {
if next_line.len() < old_col_nr { if next_line.len() <= old_col_nr {
if let Some(last_char) = next_line.chars().last() { if let Some(last_char) = next_line.chars().last() {
if is_newline(&last_char) { if is_newline(&last_char) {
(old_line_nr + 1, next_line.len() - 1) (old_line_nr + 1, next_line.len() - 1)
@ -205,10 +263,21 @@ pub fn move_caret_down(
let new_selection_opt = if shift_pressed { let new_selection_opt = if shift_pressed {
if let Some(old_selection) = old_selection_opt { if let Some(old_selection) = old_selection_opt {
if old_caret_pos <= old_selection.start_pos {
if new_caret_pos == old_selection.end_pos {
None
} else {
Some(RawSelection {
start_pos: min(old_selection.end_pos, new_caret_pos),
end_pos: max(old_selection.end_pos, new_caret_pos),
})
}
} else {
Some(RawSelection { Some(RawSelection {
start_pos: old_selection.start_pos, start_pos: old_selection.start_pos,
end_pos: new_caret_pos, end_pos: new_caret_pos,
}) })
}
} else if !(old_line_nr == line_nr && old_col_nr == col_nr) { } else if !(old_line_nr == line_nr && old_col_nr == col_nr) {
Some(RawSelection { Some(RawSelection {
start_pos: min(old_caret_pos, new_caret_pos), start_pos: min(old_caret_pos, new_caret_pos),
@ -223,3 +292,53 @@ pub fn move_caret_down(
(new_caret_pos, new_selection_opt) (new_caret_pos, new_selection_opt)
} }
pub fn update_text_state(ed_model: &mut EdModel, received_char: &char) {
ed_model.selection_opt = None;
match received_char {
'\u{8}' | '\u{7f}' => {
// In Linux, we get a '\u{8}' when you press backspace,
// but in macOS we get '\u{7f}'.
if let Some(last_line) = ed_model.lines.last_mut() {
if !last_line.is_empty() {
last_line.pop();
} else if ed_model.lines.len() > 1 {
ed_model.lines.pop();
}
ed_model.caret_pos =
move_caret_left(ed_model.caret_pos, None, false, &ed_model.lines).0;
}
}
'\u{e000}'..='\u{f8ff}' | '\u{f0000}'..='\u{ffffd}' | '\u{100000}'..='\u{10fffd}' => {
// These are private use characters; ignore them.
// See http://www.unicode.org/faq/private_use.html
}
ch if is_newline(ch) => {
if let Some(last_line) = ed_model.lines.last_mut() {
last_line.push(*received_char)
}
ed_model.lines.push(String::new());
ed_model.caret_pos = Position {
line: ed_model.caret_pos.line + 1,
column: 0,
};
ed_model.selection_opt = None;
}
_ => {
let nr_lines = ed_model.lines.len();
if let Some(last_line) = ed_model.lines.last_mut() {
last_line.push(*received_char);
ed_model.caret_pos = Position {
line: nr_lines - 1,
column: last_line.len(),
};
ed_model.selection_opt = None;
}
}
}
}

View file

@ -1,5 +1,5 @@
pub fn is_newline(char_ref: &char) -> bool { pub fn is_newline(char_ref: &char) -> bool {
let newline_codes = vec!['\u{d}']; let newline_codes = vec!['\u{d}', '\n'];
newline_codes.contains(char_ref) newline_codes.contains(char_ref)
} }

View file

@ -0,0 +1,11 @@
text = { (ASCII_ALPHANUMERIC | " " | "\t" | "\n")* }
caret = {"|"}
optSelStart = { "["{0,1} }
optSelEnd = { "]"{0,1} }
optCaret = { caret{0,1} }
linesWithSelect = { SOI ~ text ~ optCaret ~ text ~ optSelStart ~ text ~ optCaret ~ text ~ optCaret ~ text ~ optSelEnd ~ text ~ optCaret ~ text ~ EOI}

View file

@ -1,6 +1,6 @@
app "closure" provides [ makeClosure ] to "./platform/" app "closure" provides [ makeClosure ] to "./platform/"
makeClosure : ({} -> I64) as MyClosure makeClosure : ({} -> Int *) as MyClosure
makeClosure = makeClosure =
x = 42 x = 42
y = 42 y = 42

View file

@ -5,7 +5,7 @@ ConsList a : [ Cons a (ConsList a), Nil ]
empty : ConsList a empty : ConsList a
empty = Nil empty = Nil
len : ConsList a -> I64 len : ConsList a -> Int *
len = \list -> len = \list ->
when list is when list is
Cons _ rest -> 1 + len rest Cons _ rest -> 1 + len rest

View file

@ -0,0 +1,54 @@
app "effect-example"
packages { base: "thing/platform-dir" }
imports [base.Task]
provides [ main ] to base
main : Task.Task {} []
main =
queens 10
|> Str.fromInt
|> Task.putLine
ConsList a : [ Nil, Cons a (ConsList a) ]
queens = \n -> length (findSolutions n n)
length : ConsList a -> I64
length = \xs -> lengthHelp xs 0
lengthHelp : ConsList a, I64 -> I64
lengthHelp = \xs, acc ->
when xs is
Nil -> acc
Cons _ rest -> lengthHelp rest (1 + acc)
safe : I64, I64, ConsList I64 -> Bool
safe = \queen, diagonal, xs ->
when xs is
Nil ->
True
Cons q t ->
queen != q && queen != q + diagonal && queen != q - diagonal && safe queen (diagonal + 1) t
appendSafe : I64, ConsList I64, ConsList (ConsList I64) -> ConsList (ConsList I64)
appendSafe = \k, soln, solns ->
if k <= 0 then
solns
else if safe k 1 soln then
appendSafe (k - 1) soln (Cons (Cons k soln) solns)
else
appendSafe (k - 1) soln solns
extend = \n, acc, solns ->
when solns is
Nil -> acc
Cons soln rest -> extend n (appendSafe n soln acc) rest
findSolutions = \n, k ->
if k == 0 then
Cons Nil Nil
else
extend n Nil (findSolutions n (k - 1))

View file

@ -21,11 +21,11 @@ singleton = \key, value ->
Node Black key value Empty Empty Node Black key value Empty Empty
# {-| Determine the number of key-value pairs in the dictionary. -} # {-| Determine the number of key-value pairs in the dictionary. -}
size : Dict k v -> I64 size : Dict k v -> Int *
size = \dict -> size = \dict ->
sizeHelp 0 dict sizeHelp 0 dict
sizeHelp : I64, Dict k v -> I64 sizeHelp : Int *, Dict k v -> Int *
sizeHelp = \n, dict -> sizeHelp = \n, dict ->
when dict is when dict is
Empty -> Empty ->

View file

@ -52,7 +52,7 @@ pub fn roc_fx_getLine() -> RocStr {
let stdin = io::stdin(); let stdin = io::stdin();
let line1 = stdin.lock().lines().next().unwrap().unwrap(); let line1 = stdin.lock().lines().next().unwrap().unwrap();
RocStr::from_slice_with_capacity(line1.as_bytes(), line1.len()) RocStr::from_slice(line1.as_bytes())
} }
unsafe fn call_the_closure(function_pointer: *const u8, closure_data_ptr: *const u8) -> i64 { unsafe fn call_the_closure(function_pointer: *const u8, closure_data_ptr: *const u8) -> i64 {

View file

@ -5,7 +5,7 @@ app "quicksort"
quicksort = \originalList -> quicksort = \originalList ->
quicksortHelp : List (Num a), I64, I64 -> List (Num a) quicksortHelp : List (Num a), Nat, Nat -> List (Num a)
quicksortHelp = \list, low, high -> quicksortHelp = \list, low, high ->
if low < high then if low < high then
when partition low high list is when partition low high list is
@ -17,7 +17,7 @@ quicksort = \originalList ->
list list
partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ] partition : Nat, Nat, List (Num a) -> [ Pair Nat (List (Num a)) ]
partition = \low, high, initialList -> partition = \low, high, initialList ->
when List.get initialList high is when List.get initialList high is
Ok pivot -> Ok pivot ->
@ -28,7 +28,7 @@ quicksort = \originalList ->
Err _ -> Err _ ->
Pair (low - 1) initialList Pair (low - 1) initialList
partitionHelp : I64, I64, List (Num a), I64, (Num a) -> [ Pair I64 (List (Num a)) ] partitionHelp : Nat, Nat, List (Num c), Nat, (Num c) -> [ Pair Nat (List (Num c)) ]
partitionHelp = \i, j, list, high, pivot -> partitionHelp = \i, j, list, high, pivot ->
if j < high then if j < high then
when List.get list j is when List.get list j is
@ -44,7 +44,7 @@ quicksort = \originalList ->
Pair i list Pair i list
swap : I64, I64, List a -> List a swap : Nat, Nat, List a -> List a
swap = \i, j, list -> swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) -> Pair (Ok atI) (Ok atJ) ->

View file

@ -1,12 +1,12 @@
app "quicksort" packages { base: "./platform" } provides [ quicksort ] to base app "quicksort" packages { base: "./platform" } provides [ quicksort ] to base
quicksort : List I64 -> List I64 quicksort : List Int * -> List Int *
quicksort = \originalList -> helper originalList quicksort = \originalList -> helper originalList
helper : List I64 -> List I64 helper : List Int * -> List Int *
helper = \originalList -> helper = \originalList ->
quicksortHelp : List (Num a), I64, I64 -> List (Num a) quicksortHelp : List (Num a), Nat, Nat -> List (Num a)
quicksortHelp = \list, low, high -> quicksortHelp = \list, low, high ->
if low < high then if low < high then
when partition low high list is when partition low high list is
@ -18,7 +18,7 @@ helper = \originalList ->
list list
swap : I64, I64, List a -> List a swap : Nat, Nat, List a -> List a
swap = \i, j, list -> swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) -> Pair (Ok atI) (Ok atJ) ->
@ -29,7 +29,7 @@ helper = \originalList ->
_ -> _ ->
[] []
partition : I64, I64, List (Num a) -> [ Pair I64 (List (Num a)) ] partition : Nat, Nat, List (Num a) -> [ Pair Nat (List (Num a)) ]
partition = \low, high, initialList -> partition = \low, high, initialList ->
when List.get initialList high is when List.get initialList high is
Ok pivot -> Ok pivot ->
@ -41,7 +41,7 @@ helper = \originalList ->
Pair (low - 1) initialList Pair (low - 1) initialList
partitionHelp : I64, I64, List (Num a), I64, (Num a) -> [ Pair I64 (List (Num a)) ] partitionHelp : Nat, Nat, List (Num a), Nat, (Num a) -> [ Pair Nat (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot -> partitionHelp = \i, j, list, high, pivot ->
if j < high then if j < high then
when List.get list j is when List.get list j is

View file

@ -1,10 +1,8 @@
{ pkgs, isMacOS, isAarch64 }: { pkgs, isMacOS, isAarch64 }:
# We require at least specific commit of Zig after the latest tagged if isMacOS then
# release (0.6.0), so we just download the binaries for that commit let
version = "0.7.1";
let
version = "0.6.0+0088efc4b";
osName = osName =
if isMacOS if isMacOS
then "macos" then "macos"
@ -14,14 +12,9 @@ let
then "aarch64" then "aarch64"
else "x86_64"; else "x86_64";
archiveName = "zig-${osName}-${archName}-${version}"; archiveName = "zig-${osName}-${archName}-${version}";
sha256 = sha256 = "845cb17562978af0cf67e3993f4e33330525eaf01ead9386df9105111e3bc519";
if isMacOS in
then "665c1a7f472cfc5e0715f0ddf6ff8409fb749ac91cbbae68c443b4a37ebd058e" pkgs.stdenv.mkDerivation {
else if isAarch64
then "116ms44vx4xz57m9z9lsgrxd1g22qp00m5qbmklky8xdd2jmj24w"
else "bab70ae3bd0af538022bc3ef50d8f34fa8dceac39ba7d9e5d528eee7e6d5a1cf";
in
pkgs.stdenv.mkDerivation {
pname = "zig"; pname = "zig";
version = version; version = version;
buildInputs = [ pkgs.gzip ]; buildInputs = [ pkgs.gzip ];
@ -39,4 +32,6 @@ pkgs.stdenv.mkDerivation {
ln -s "$out/zig" "$out/bin/zig" ln -s "$out/zig" "$out/bin/zig"
chmod +x $out/bin/zig chmod +x $out/bin/zig
''; '';
} }
else
pkgs.zig

View file

@ -99,26 +99,26 @@ impl<T> RocList<T> {
self.get_storage_ptr() as *mut usize self.get_storage_ptr() as *mut usize
} }
fn get_element_ptr<Q>(elements: *const Q) -> *const usize { fn get_element_ptr(elements: *const T) -> *const T {
let elem_alignment = core::mem::align_of::<T>(); let elem_alignment = core::mem::align_of::<T>();
let ptr = elements as *const usize; let ptr = elements as *const usize;
unsafe { unsafe {
if elem_alignment <= core::mem::align_of::<usize>() { if elem_alignment <= core::mem::align_of::<usize>() {
ptr.offset(1) ptr.offset(1) as *const T
} else { } else {
// If elements have an alignment bigger than usize (e.g. an i128), // If elements have an alignment bigger than usize (e.g. an i128),
// we will have necessarily allocated two usize slots worth of // we will have necessarily allocated two usize slots worth of
// space for the storage value (with the first usize slot being // space for the storage value (with the first usize slot being
// padding for alignment's sake), and we need to skip past both. // padding for alignment's sake), and we need to skip past both.
ptr.offset(2) ptr.offset(2) as *const T
} }
} }
} }
pub fn from_slice_with_capacity(slice: &[T], capacity: usize) -> RocList<T> pub fn from_slice_with_capacity(slice: &[T], capacity: usize) -> RocList<T>
where where
T: Copy, T: Clone,
{ {
assert!(slice.len() <= capacity); assert!(slice.len() <= capacity);
@ -138,25 +138,37 @@ impl<T> RocList<T> {
let num_bytes = core::mem::size_of::<usize>() + padding + element_bytes; let num_bytes = core::mem::size_of::<usize>() + padding + element_bytes;
let elements = unsafe { let elements = unsafe {
let raw_ptr = libc::malloc(num_bytes); let raw_ptr = libc::malloc(num_bytes) as *mut u8;
// pointer to the first element
let raw_ptr = Self::get_element_ptr(raw_ptr as *mut T) as *mut T;
// write the capacity // write the capacity
let capacity_ptr = raw_ptr as *mut usize; let capacity_ptr = raw_ptr as *mut usize;
*capacity_ptr = capacity; *(capacity_ptr.offset(-1)) = capacity;
let raw_ptr = Self::get_element_ptr(raw_ptr as *mut T);
{ {
// NOTE: using a memcpy here causes weird issues // NOTE: using a memcpy here causes weird issues
let target_ptr = raw_ptr as *mut T; let target_ptr = raw_ptr as *mut T;
let source_ptr = ptr as *const T; let source_ptr = ptr as *const T;
let length = slice.len() as isize; for index in 0..slice.len() {
for index in 0..length { let source = &*source_ptr.add(index);
*target_ptr.offset(index) = *source_ptr.offset(index); let target = &mut *target_ptr.add(index);
// NOTE for a weird reason, it's important that we clone onto the stack
// and explicitly forget the swapped-in value
// cloning directly from source to target causes some garbage memory (cast to a
// RocStr) to end up in the drop implementation of RocStr and cause havoc by
// freeing NULL
let mut temporary = source.clone();
core::mem::swap(target, &mut temporary);
core::mem::forget(temporary);
} }
} }
raw_ptr as *mut T raw_ptr
}; };
RocList { RocList {
@ -167,7 +179,7 @@ impl<T> RocList<T> {
pub fn from_slice(slice: &[T]) -> RocList<T> pub fn from_slice(slice: &[T]) -> RocList<T>
where where
T: Copy, T: Clone,
{ {
Self::from_slice_with_capacity(slice, slice.len()) Self::from_slice_with_capacity(slice, slice.len())
} }
@ -328,21 +340,27 @@ impl RocStr {
(self as *mut RocStr).cast() (self as *mut RocStr).cast()
} }
pub fn from_slice_with_capacity(slice: &[u8], capacity: usize) -> RocStr { fn from_slice_with_capacity_str(slice: &[u8], capacity: usize) -> RocStr {
assert!(slice.len() <= capacity); assert!(
slice.len() <= capacity,
"RocStr::from_slice_with_capacity_str length bigger than capacity {} {}",
slice.len(),
capacity
);
if capacity < core::mem::size_of::<RocStr>() { if capacity < core::mem::size_of::<RocStr>() {
let mut rocstr = RocStr::empty(); let mut rocstr = RocStr::empty();
let target_ptr = rocstr.get_small_str_ptr_mut(); let target_ptr = rocstr.get_small_str_ptr_mut();
let source_ptr = slice.as_ptr() as *const u8; let source_ptr = slice.as_ptr() as *const u8;
for index in 0..(slice.len() as isize) { for index in 0..slice.len() {
unsafe { unsafe {
*target_ptr.offset(index) = *source_ptr.offset(index); *target_ptr.add(index) = *source_ptr.add(index);
} }
} }
// Write length and small string bit to last byte of length. // Write length and small string bit to last byte of length.
let mut bytes = rocstr.length.to_ne_bytes(); let mut bytes = rocstr.length.to_ne_bytes();
bytes[bytes.len() - 1] = capacity as u8 ^ 0b1000_0000; bytes[bytes.len() - 1] = capacity as u8 ^ 0b1000_0000;
rocstr.length = usize::from_ne_bytes(bytes); rocstr.length = usize::from_ne_bytes(bytes);
rocstr rocstr
} else { } else {
let ptr = slice.as_ptr(); let ptr = slice.as_ptr();
@ -380,7 +398,7 @@ impl RocStr {
} }
pub fn from_slice(slice: &[u8]) -> RocStr { pub fn from_slice(slice: &[u8]) -> RocStr {
Self::from_slice_with_capacity(slice, slice.len()) Self::from_slice_with_capacity_str(slice, slice.len())
} }
pub fn as_slice(&self) -> &[u8] { pub fn as_slice(&self) -> &[u8] {
@ -423,6 +441,38 @@ impl PartialEq for RocStr {
impl Eq for RocStr {} impl Eq for RocStr {}
impl Clone for RocStr {
fn clone(&self) -> Self {
if self.is_small_str() || self.is_empty() {
Self {
elements: self.elements,
length: self.length,
}
} else {
let capacity_size = core::mem::size_of::<usize>();
let copy_length = self.length + capacity_size;
let elements = unsafe {
let raw = libc::malloc(copy_length);
libc::memcpy(
raw,
self.elements.offset(-(capacity_size as isize)) as *mut libc::c_void,
copy_length,
);
*(raw as *mut usize) = self.length;
(raw as *mut u8).add(capacity_size)
};
Self {
elements,
length: self.length,
}
}
}
}
impl Drop for RocStr { impl Drop for RocStr {
fn drop(&mut self) { fn drop(&mut self) {
if !self.is_small_str() { if !self.is_small_str() {

View file

@ -8,10 +8,10 @@ in with {
# Look here for information about how pin version of nixpkgs # Look here for information about how pin version of nixpkgs
# → https://nixos.wiki/wiki/FAQ/Pinning_Nixpkgs # → https://nixos.wiki/wiki/FAQ/Pinning_Nixpkgs
pkgs = import (builtins.fetchGit { pkgs = import (builtins.fetchGit {
name = "nixpkgs-2020-11-24"; name = "nixpkgs-2021-01-05";
url = "https://github.com/nixos/nixpkgs/"; url = "https://github.com/nixos/nixpkgs/";
ref = "refs/heads/nixpkgs-unstable"; ref = "refs/heads/nixpkgs-unstable";
rev = "6625284c397b44bc9518a5a1567c1b5aae455c08"; rev = "f53c431645da8e6760268092aa10f736b5cfb117";
}) { }; }) { };
isMacOS = currentOS == "darwin"; isMacOS = currentOS == "darwin";