diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e2b119c59f..667c3ef007 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,9 @@ jobs: steps: - uses: actions/checkout@v1 + - name: Verify compiler/builtin/bitcode/regenerate.sh was run if necessary + run: pushd compiler/builtins/bitcode && ./regenerate.sh && git diff --exit-code ../../gen/src/llvm/builtins.bc && popd + - name: Install LLVM run: sudo ./ci/install-llvm.sh 10 diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index c1377fc87c..1a521cab83 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -1,9 +1,9 @@ # Building the Roc compiler from source -## Installing LLVM +## Installing LLVM and libc++abi -To build the compiler, you need a particular version of LLVM installed on your system. +To build the compiler, you need both `libc++abi` and a particular version of LLVM installed on your system. Some systems may already have `libc++abi` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `apt-get install libc++abi-dev`.) To see which version of LLVM you need, take a look at `Cargo.toml`, in particular the `branch` section of the `inkwell` dependency. It should have something like `llvmX-Y` where X and Y are the major and minor revisions of LLVM you need. @@ -36,6 +36,8 @@ $ brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/6616d50f $ brew pin llvm ``` +If that doesn't work and you get a `brew` error `Error: Calling Installation of llvm from a GitHub commit URL is disabled! Use 'brew extract llvm' to stable tap on GitHub instead.` while trying the above solution, you can follow the steps extracting the formula into your private tap (one public version is at `sladwig/tap/llvm`). If installing LLVM still fails, it might help to run `sudo xcode-select -r` before installing again. + ### LLVM installation on Windows Installing LLVM's prebuilt binaries doesn't seem to be enough for the `llvm-sys` crate that Roc depends on, so I had to build LLVM from source diff --git a/Cargo.lock b/Cargo.lock index 2c0bf8aef8..f54106f304 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1112,7 +1112,7 @@ dependencies = [ [[package]] name = "inkwell" version = "0.1.0" -source = "git+https://github.com/rtfeldman/inkwell?tag=llvm10-0.release1#005de45445d6ddae80f09ed012e6c3dfe674201a" +source = "git+https://github.com/rtfeldman/inkwell?tag=llvm10-0.release2#d0a1ce5e678cf0f0e62b757760d1019301c603c9" dependencies = [ "either", "inkwell_internals", @@ -1126,7 +1126,7 @@ dependencies = [ [[package]] name = "inkwell_internals" version = "0.1.0" -source = "git+https://github.com/rtfeldman/inkwell?tag=llvm10-0.release1#005de45445d6ddae80f09ed012e6c3dfe674201a" +source = "git+https://github.com/rtfeldman/inkwell?tag=llvm10-0.release2#d0a1ce5e678cf0f0e62b757760d1019301c603c9" dependencies = [ "proc-macro2 0.4.30", "quote 0.6.13", diff --git a/README.md b/README.md index 593d71461a..b559f0b21f 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ One of the reasons this editor is coupled with the language itself is to allow p A trivial example: suppose I'm writing a Roc app for an Arduino platform. I install a platform-specific package for displaying text on a grid of LEDs. Because I've installed this package, at the call site where I call the function to specify the color of the text on the LEDs, my Roc editor displays an inline color picker. As I move a slider around to try out different colors, not only does my code change to reflect that value in realtime, but the physical LEDs in my room change color in realtime as well. As the application author, all I did to get that experience was to install the "text on an LED grid" package, nothing else. -The goal is for this to be one of the most trivial, bare minimum examples of what the editor experience would be like. Hopefully, people in the future will look back on this example and say "that's so embarassingly basic; why didn't you talk about one of the *actually great* things in the seamless editor plugin ecosystem?" +The goal is for this to be one of the most trivial, bare minimum examples of what the editor experience would be like. Hopefully, people in the future will look back on this example and say "that's so embarrassingly basic; why didn't you talk about one of the *actually great* things in the seamless editor plugin ecosystem?" Finally, some implementation goals: diff --git a/cli/Cargo.toml b/cli/Cargo.toml index a204f93e2f..ac1cb022dd 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -76,7 +76,7 @@ libc = "0.2" # commit of TheDan64/inkwell, push a new tag which points to the latest commit, # change the tag value in this Cargo.toml to point to that tag, and `cargo update`. # This way, GitHub Actions works and nobody's builds get broken. -inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release1" } +inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release2" } target-lexicon = "0.10" [dev-dependencies] diff --git a/cli/src/lib.rs b/cli/src/lib.rs index ce97bc90f3..b84b6ed76a 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -210,6 +210,12 @@ fn build_file( "host.rs", "-o", binary_path.as_path().to_str().unwrap(), + // ensure we don't make a position-independent executable + "-C", + "link-arg=-no-pie", + // explicitly link in the c++ stdlib, for exceptions + "-C", + "link-arg=-lc++", ]) .current_dir(cwd) .spawn() diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 29b483f92e..dff5e26661 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -1,7 +1,8 @@ use bumpalo::collections::Vec; use bumpalo::Bump; -use inkwell::execution_engine::{ExecutionEngine, JitFunction}; +use inkwell::execution_engine::ExecutionEngine; use roc_collections::all::MutMap; +use roc_gen::{run_jit_function, run_jit_function_dynamic_type}; use roc_module::ident::{Lowercase, TagName}; use roc_module::operator::CalledVia; use roc_module::symbol::{Interns, ModuleId, Symbol}; @@ -50,20 +51,6 @@ pub unsafe fn jit_to_ast<'a>( jit_to_ast_help(&env, execution_engine, main_fn_name, layout, content) } -macro_rules! jit_map { - ($execution_engine: expr, $main_fn_name: expr, $ty: ty, $transform: expr) => {{ - unsafe { - let main: JitFunction $ty> = $execution_engine - .get_function($main_fn_name) - .ok() - .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) - .expect("errored"); - - $transform(main.call()) - } - }}; -} - fn jit_to_ast_help<'a>( env: &Env<'a, '_>, execution_engine: ExecutionEngine, @@ -72,32 +59,30 @@ fn jit_to_ast_help<'a>( content: &Content, ) -> Expr<'a> { match layout { - Layout::Builtin(Builtin::Int64) => { - jit_map!(execution_engine, main_fn_name, i64, |num| num_to_ast( - env, - i64_to_ast(env.arena, num), - content - )) - } - Layout::Builtin(Builtin::Float64) => { - jit_map!(execution_engine, main_fn_name, f64, |num| num_to_ast( - env, - f64_to_ast(env.arena, num), - content - )) - } - Layout::Builtin(Builtin::Str) | Layout::Builtin(Builtin::EmptyStr) => jit_map!( + Layout::Builtin(Builtin::Int64) => run_jit_function!( + execution_engine, + main_fn_name, + i64, + |num| num_to_ast(env, i64_to_ast(env.arena, num), content) + ), + Layout::Builtin(Builtin::Float64) => run_jit_function!( + execution_engine, + main_fn_name, + f64, + |num| num_to_ast(env, f64_to_ast(env.arena, num), content) + ), + Layout::Builtin(Builtin::Str) | Layout::Builtin(Builtin::EmptyStr) => run_jit_function!( execution_engine, main_fn_name, &'static str, |string: &'static str| { str_to_ast(env.arena, env.arena.alloc(string)) } ), Layout::Builtin(Builtin::EmptyList) => { - jit_map!(execution_engine, main_fn_name, &'static str, |_| { + run_jit_function!(execution_engine, main_fn_name, &'static str, |_| { Expr::List(Vec::new_in(env.arena)) }) } - Layout::Builtin(Builtin::List(_, elem_layout)) => jit_map!( + Layout::Builtin(Builtin::List(_, elem_layout)) => run_jit_function!( execution_engine, main_fn_name, (*const libc::c_void, usize), @@ -118,18 +103,26 @@ fn jit_to_ast_help<'a>( } }; + let fields = [Layout::Builtin(Builtin::Int64), layout.clone()]; + let layout = Layout::Struct(&fields); + match env.ptr_bytes { // 64-bit target (8-byte pointers, 16-byte structs) 8 => match layout.stack_size(env.ptr_bytes) { 8 => { // just one eightbyte, returned as-is - jit_map!(execution_engine, main_fn_name, [u8; 8], |bytes: [u8; 8]| { - ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) - }) + run_jit_function!( + execution_engine, + main_fn_name, + [u8; 8], + |bytes: [u8; 8]| { + ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) + } + ) } 16 => { // two eightbytes, returned as-is - jit_map!( + run_jit_function!( execution_engine, main_fn_name, [u8; 16], @@ -138,13 +131,13 @@ fn jit_to_ast_help<'a>( } ) } - _ => { + larger_size => { // anything more than 2 eightbytes // the return "value" is a pointer to the result - jit_map!( + run_jit_function_dynamic_type!( execution_engine, main_fn_name, - *const u8, + larger_size as usize, |bytes: *const u8| { ptr_to_ast(bytes as *const libc::c_void) } ) } @@ -157,23 +150,33 @@ fn jit_to_ast_help<'a>( match layout.stack_size(env.ptr_bytes) { 4 => { // just one fourbyte, returned as-is - jit_map!(execution_engine, main_fn_name, [u8; 4], |bytes: [u8; 4]| { - ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) - }) + run_jit_function!( + execution_engine, + main_fn_name, + [u8; 4], + |bytes: [u8; 4]| { + ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) + } + ) } 8 => { // just one fourbyte, returned as-is - jit_map!(execution_engine, main_fn_name, [u8; 8], |bytes: [u8; 8]| { - ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) - }) - } - _ => { - // anything more than 2 fourbytes - // the return "value" is a pointer to the result - jit_map!( + run_jit_function!( execution_engine, main_fn_name, - *const u8, + [u8; 8], + |bytes: [u8; 8]| { + ptr_to_ast((&bytes).as_ptr() as *const libc::c_void) + } + ) + } + larger_size => { + // anything more than 2 fourbytes + // the return "value" is a pointer to the result + run_jit_function_dynamic_type!( + execution_engine, + main_fn_name, + larger_size as usize, |bytes: *const u8| { ptr_to_ast(bytes as *const libc::c_void) } ) } diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 00d92de0a4..cf44b14247 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -1,4 +1,4 @@ -#[macro_use] +// #[macro_use] extern crate pretty_assertions; extern crate bumpalo; @@ -20,7 +20,9 @@ mod cli_run { example_file("hello-world", "Hello.roc").to_str().unwrap(), ]); - assert_eq!(&out.stderr, ""); + if !out.stderr.is_empty() { + panic!(out.stderr); + } assert!(&out.stdout.ends_with("Hello, World!!!!!!!!!!!!!\n")); assert!(out.status.success()); } @@ -33,7 +35,9 @@ mod cli_run { "--optimize", ]); - assert_eq!(&out.stderr, ""); + if !out.stderr.is_empty() { + panic!(out.stderr); + } assert!(&out .stdout .ends_with("[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n")); diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index f867800665..ffadf96e7f 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -241,6 +241,15 @@ mod repl_eval { ); } + #[test] + fn four_element_record() { + // if this tests turns out to fail on 32-bit platforms, look at jit_to_ast_help + expect_success( + "{ a: 1, b: 2, c: 3, d: 4 }", + "{ a: 1, b: 2, c: 3, d: 4 } : { a : Num *, b : Num *, c : Num *, d : Num * }", + ); + } + // #[test] // fn multiline_string() { // // If a string contains newlines, format it as a multiline string in the output diff --git a/compiler/README.md b/compiler/README.md index 59ebb089fc..b4a1c50442 100644 --- a/compiler/README.md +++ b/compiler/README.md @@ -26,7 +26,7 @@ This `Expr` representation of the expression is useful for things like: Since the parser is only concerned with translating String values into Expr values, it will happily translate syntactically valid strings into expressions that won't work at runtime. -For example, parsing will transalte this string: +For example, parsing will translate this string: not "foo", "bar" @@ -87,11 +87,11 @@ The first thing it does is to call `eval` on the right `Expr` values on either s Since the second call to `eval` will match on another `BinOp`, it's once again going to recursively call `eval` on both of its `Expr` values. Since those are both `Int` values, though, their `eval` calls will return them right away without doing anything else. -Now that it's evaluated the expressions on either side of the `Minus`, `eval` will look at the particular operator being applied to those expressoins (in this case, a minus operator) and check to see if the expressions it was given work with that operation. +Now that it's evaluated the expressions on either side of the `Minus`, `eval` will look at the particular operator being applied to those expressions (in this case, a minus operator) and check to see if the expressions it was given work with that operation. > Remember, this `Expr` value potentially came directly from the parser. `eval` can't be sure any type checking has been done on it! -If `eval` detects a non-numeric `Expr` value (that is, the `Expr` is not `Int` or `Frac`) on either side of the `Mnus`, then it will immediately give an error and halt the evaluation. This sort of runtime type error is common to dynamic languages, and you can think of `eval` as being a dynamic evaluation of Roc code that hasn't necessarily been type-checked. +If `eval` detects a non-numeric `Expr` value (that is, the `Expr` is not `Int` or `Frac`) on either side of the `Minus`, then it will immediately give an error and halt the evaluation. This sort of runtime type error is common to dynamic languages, and you can think of `eval` as being a dynamic evaluation of Roc code that hasn't necessarily been type-checked. Assuming there's no type problem, `eval` can go ahead and run the Rust code of `8 - 3` and store the result in an `Int` expr. diff --git a/compiler/build/Cargo.toml b/compiler/build/Cargo.toml index b6c81d8c15..5c22fa8f4f 100644 --- a/compiler/build/Cargo.toml +++ b/compiler/build/Cargo.toml @@ -44,7 +44,7 @@ tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", # commit of TheDan64/inkwell, push a new tag which points to the latest commit, # change the tag value in this Cargo.toml to point to that tag, and `cargo update`. # This way, GitHub Actions works and nobody's builds get broken. -inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release1" } +inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release2" } target-lexicon = "0.10" [dev-dependencies] diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 223b862e32..2a4ba3dde9 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -273,6 +273,9 @@ pub fn gen( } } + // Uncomment this to see the module's optimized LLVM instruction output: + // env.module.print_to_stderr(); + mpm.run_on(module); // Verify the module diff --git a/compiler/builtins/bitcode/README.md b/compiler/builtins/bitcode/README.md index da1dabea44..823c734851 100644 --- a/compiler/builtins/bitcode/README.md +++ b/compiler/builtins/bitcode/README.md @@ -17,42 +17,37 @@ The source we'll use to generate the bitcode is in `src/lib.rs` in this director To generate the bitcode, `cd` into `compiler/builtins/bitcode/` and run: ```bash -$ cargo rustc --release --lib -- --emit=llvm-bc +$ ./regenerate.sh ``` -Then look in the root `roc` source directory under `target/release/deps/` for a file -with a name like `roc_builtins_bitcode-8da0901c58a73ebf.bc` - except -probably with a different hash before the `.bc`. If there's more than one -`*.bc` file in that directory, delete the whole `deps/` directory and re-run -the `cargo rustc` command above to regenerate it. - > If you want to take a look at the human-readable LLVM IR rather than the > bitcode, run this instead and look for a `.ll` file instead of a `.bc` file: > > ```bash > $ cargo rustc --release --lib -- --emit=llvm-ir > ``` +> +> Then look in the root `roc` source directory under `target/release/deps/` for a file +> with a name like `roc_builtins_bitcode-8da0901c58a73ebf.bc` - except +> probably with a different hash before the `.bc`. If there's more than one +> `*.bc` file in that directory, delete the whole `deps/` directory and re-run +> the `cargo rustc` command above to regenerate it. **Note**: In order to be able to address the bitcode functions by name, they need to be defined with the `#[no_mangle]` attribute. -## Importing the bitcode - The bitcode is a bunch of bytes that aren't particularly human-readable. Since Roc is designed to be distributed as a single binary, these bytes need to be included in the raw source somewhere. The `llvm/src/build.rs` file statically imports these raw bytes -using the [`include_bytes!` macro](https://doc.rust-lang.org/std/macro.include_bytes.html), -so we just need to move the `.bc` file from the previous step to the correct -location. - +using the [`include_bytes!` macro](https://doc.rust-lang.org/std/macro.include_bytes.html). The current `.bc` file is located at: ``` compiler/gen/src/llvm/builtins.bc ``` -...so you want to overwrite it with the new `.bc` file in `target/deps/` +The script will automatically replace this `.bc` file with the new one. Once that's done, `git status` should show that the `builtins.bc` file has been changed. Commit that change and you're done! diff --git a/compiler/builtins/bitcode/regenerate.sh b/compiler/builtins/bitcode/regenerate.sh new file mode 100755 index 0000000000..f4d7a0cd9a --- /dev/null +++ b/compiler/builtins/bitcode/regenerate.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -euxo pipefail + +# Clear out any existing output files. Sometimes if these are there, rustc +# doesn't generate the .bc file - or we can end up with more than one .bc +rm -rf ../../../target/release/deps/ + +# Regenerate the .bc file +cargo rustc --release --lib -- --emit=llvm-bc + +bc_files=$(ls ../../../target/release/deps/*.bc | wc -l) + +if [[ $bc_files != 1 ]]; then + echo "More than one .bc file was emitted somehow." + exit 1; +fi + +cp ../../../target/release/deps/*.bc ../../gen/src/llvm/builtins.bc diff --git a/compiler/builtins/bitcode/src/lib.rs b/compiler/builtins/bitcode/src/lib.rs index 2316d1265b..57dfd48ba8 100644 --- a/compiler/builtins/bitcode/src/lib.rs +++ b/compiler/builtins/bitcode/src/lib.rs @@ -4,6 +4,8 @@ #![crate_type = "lib"] #![no_std] +mod libm; + /// TODO replace this with a normal Inkwell build_cast call - this was just /// used as a proof of concept for getting bitcode importing working! #[no_mangle] @@ -36,3 +38,17 @@ pub fn pow_int_(mut base: i64, mut exp: i64) -> i64 { acc } + +/// Adapted from Rust's core::num module, by the Rust core team, +/// licensed under the Apache License, version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 +/// +/// Thank you, Rust core team! +#[no_mangle] +pub fn is_finite_(num: f64) -> bool { + f64::is_finite(num) +} + +#[no_mangle] +pub fn atan_(x: f64) -> f64 { + libm::atan(x) +} diff --git a/compiler/builtins/bitcode/src/libm.rs b/compiler/builtins/bitcode/src/libm.rs new file mode 100644 index 0000000000..67cc4d942d --- /dev/null +++ b/compiler/builtins/bitcode/src/libm.rs @@ -0,0 +1,199 @@ +/// Adapted from Rust's libm module, by the Rust core team, +/// licensed under the Apache License, version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 +/// https://github.com/rust-lang/libm/blob/master/LICENSE-APACHE +/// +/// Thank you, Rust core team! + +// From https://github.com/rust-lang/libm/blob/master/src/math/mod.rs +#[cfg(not(debug_assertions))] +macro_rules! i { + ($array:expr, $index:expr) => { + unsafe { *$array.get_unchecked($index) } + }; + ($array:expr, $index:expr, = , $rhs:expr) => { + unsafe { + *$array.get_unchecked_mut($index) = $rhs; + } + }; + ($array:expr, $index:expr, += , $rhs:expr) => { + unsafe { + *$array.get_unchecked_mut($index) += $rhs; + } + }; + ($array:expr, $index:expr, -= , $rhs:expr) => { + unsafe { + *$array.get_unchecked_mut($index) -= $rhs; + } + }; + ($array:expr, $index:expr, &= , $rhs:expr) => { + unsafe { + *$array.get_unchecked_mut($index) &= $rhs; + } + }; + ($array:expr, $index:expr, == , $rhs:expr) => { + unsafe { *$array.get_unchecked_mut($index) == $rhs } + }; +} + +#[cfg(debug_assertions)] +macro_rules! i { + ($array:expr, $index:expr) => { + *$array.get($index).unwrap() + }; + ($array:expr, $index:expr, = , $rhs:expr) => { + *$array.get_mut($index).unwrap() = $rhs; + }; + ($array:expr, $index:expr, -= , $rhs:expr) => { + *$array.get_mut($index).unwrap() -= $rhs; + }; + ($array:expr, $index:expr, += , $rhs:expr) => { + *$array.get_mut($index).unwrap() += $rhs; + }; + ($array:expr, $index:expr, &= , $rhs:expr) => { + *$array.get_mut($index).unwrap() &= $rhs; + }; + ($array:expr, $index:expr, == , $rhs:expr) => { + *$array.get_mut($index).unwrap() == $rhs + }; +} + +macro_rules! llvm_intrinsically_optimized { + (#[cfg($($clause:tt)*)] $e:expr) => { + #[cfg(all(feature = "unstable", $($clause)*))] + { + if true { // thwart the dead code lint + $e + } + } + }; +} + +macro_rules! force_eval { + ($e:expr) => { + unsafe { + ::core::ptr::read_volatile(&$e); + } + }; +} + +// From https://github.com/rust-lang/libm/blob/master/src/math/atan.rs + +// Clippy fails CI if we don't include overrides below. Since this is copied +// straight from the libm crate, I figure they had a reason to include +// the extra percision, so we just silence this warning. + +#[allow(clippy::excessive_precision)] +const ATANHI: [f64; 4] = [ + 4.63647609000806093515e-01, /* atan(0.5)hi 0x3FDDAC67, 0x0561BB4F */ + 7.85398163397448278999e-01, /* atan(1.0)hi 0x3FE921FB, 0x54442D18 */ + 9.82793723247329054082e-01, /* atan(1.5)hi 0x3FEF730B, 0xD281F69B */ + 1.57079632679489655800e+00, /* atan(inf)hi 0x3FF921FB, 0x54442D18 */ +]; + +#[allow(clippy::excessive_precision)] +const ATANLO: [f64; 4] = [ + 2.26987774529616870924e-17, /* atan(0.5)lo 0x3C7A2B7F, 0x222F65E2 */ + 3.06161699786838301793e-17, /* atan(1.0)lo 0x3C81A626, 0x33145C07 */ + 1.39033110312309984516e-17, /* atan(1.5)lo 0x3C700788, 0x7AF0CBBD */ + 6.12323399573676603587e-17, /* atan(inf)lo 0x3C91A626, 0x33145C07 */ +]; + +#[allow(clippy::excessive_precision)] +const AT: [f64; 11] = [ + 3.33333333333329318027e-01, /* 0x3FD55555, 0x5555550D */ + -1.99999999998764832476e-01, /* 0xBFC99999, 0x9998EBC4 */ + 1.42857142725034663711e-01, /* 0x3FC24924, 0x920083FF */ + -1.11111104054623557880e-01, /* 0xBFBC71C6, 0xFE231671 */ + 9.09088713343650656196e-02, /* 0x3FB745CD, 0xC54C206E */ + -7.69187620504482999495e-02, /* 0xBFB3B0F2, 0xAF749A6D */ + 6.66107313738753120669e-02, /* 0x3FB10D66, 0xA0D03D51 */ + -5.83357013379057348645e-02, /* 0xBFADDE2D, 0x52DEFD9A */ + 4.97687799461593236017e-02, /* 0x3FA97B4B, 0x24760DEB */ + -3.65315727442169155270e-02, /* 0xBFA2B444, 0x2C6A6C2F */ + 1.62858201153657823623e-02, /* 0x3F90AD3A, 0xE322DA11 */ +]; + +#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)] +pub fn fabs(x: f64) -> f64 { + // On wasm32 we know that LLVM's intrinsic will compile to an optimized + // `f64.abs` native instruction, so we can leverage this for both code size + // and speed. + llvm_intrinsically_optimized! { + #[cfg(target_arch = "wasm32")] { + return unsafe { ::core::intrinsics::fabsf64(x) } + } + } + f64::from_bits(x.to_bits() & (u64::MAX / 2)) +} + +#[inline(always)] +#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)] +pub fn atan(x: f64) -> f64 { + let mut x = x; + let mut ix = (x.to_bits() >> 32) as u32; + let sign = ix >> 31; + ix &= 0x7fff_ffff; + if ix >= 0x4410_0000 { + if x.is_nan() { + return x; + } + + let z = ATANHI[3] + f64::from_bits(0x0380_0000); // 0x1p-120f + return if sign != 0 { -z } else { z }; + } + + let id = if ix < 0x3fdc_0000 { + /* |x| < 0.4375 */ + if ix < 0x3e40_0000 { + /* |x| < 2^-27 */ + if ix < 0x0010_0000 { + /* raise underflow for subnormal x */ + force_eval!(x as f32); + } + + return x; + } + + -1 + } else { + x = fabs(x); + if ix < 0x3ff30000 { + /* |x| < 1.1875 */ + if ix < 0x3fe60000 { + /* 7/16 <= |x| < 11/16 */ + x = (2. * x - 1.) / (2. + x); + 0 + } else { + /* 11/16 <= |x| < 19/16 */ + x = (x - 1.) / (x + 1.); + 1 + } + } else if ix < 0x40038000 { + /* |x| < 2.4375 */ + x = (x - 1.5) / (1. + 1.5 * x); + 2 + } else { + /* 2.4375 <= |x| < 2^66 */ + x = -1. / x; + 3 + } + }; + + let z = x * x; + let w = z * z; + /* break sum from i=0 to 10 AT[i]z**(i+1) into odd and even poly */ + let s1 = z * (AT[0] + w * (AT[2] + w * (AT[4] + w * (AT[6] + w * (AT[8] + w * AT[10]))))); + let s2 = w * (AT[1] + w * (AT[3] + w * (AT[5] + w * (AT[7] + w * AT[9])))); + + if id < 0 { + return x - x * (s1 + s2); + } + + let z = i!(ATANHI, id as usize) - (x * (s1 + s2) - i!(ATANLO, id as usize) - x); + + if sign != 0 { + -z + } else { + z + } +} diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index a46e1c842c..bc9e833b31 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -187,6 +187,26 @@ pub fn types() -> MutMap { ), ); + // addChecked : Num a, Num a -> Result (Num a) [ IntOverflow ]* + let overflow = SolvedType::TagUnion( + vec![(TagName::Global("Overflow".into()), vec![])], + Box::new(SolvedType::Wildcard), + ); + + add_type( + Symbol::NUM_ADD_CHECKED, + SolvedType::Func( + vec![num_type(flex(TVAR1)), num_type(flex(TVAR1))], + Box::new(result_type(num_type(flex(TVAR1)), overflow)), + ), + ); + + // addWrap : Int, Int -> Int + add_type( + Symbol::NUM_ADD_WRAP, + SolvedType::Func(vec![int_type(), int_type()], Box::new(int_type())), + ); + // sub or (-) : Num a, Num a -> Num a add_type( Symbol::NUM_SUB, @@ -436,6 +456,12 @@ pub fn types() -> MutMap { SolvedType::Func(vec![float_type()], Box::new(int_type())), ); + // atan : Float -> Float + add_type( + Symbol::NUM_ATAN, + SolvedType::Func(vec![float_type()], Box::new(float_type())), + ); + // Bool module // and : Bool, Bool -> Bool diff --git a/compiler/builtins/src/unique.rs b/compiler/builtins/src/unique.rs index a04bc1bfdc..a7fd715fc5 100644 --- a/compiler/builtins/src/unique.rs +++ b/compiler/builtins/src/unique.rs @@ -274,6 +274,26 @@ pub fn types() -> MutMap { unique_function(vec![num_type(u, num), num_type(v, num)], num_type(w, num)) }); + // addChecked : Num a, Num a -> Result (Num a) [ IntOverflow ]* + let overflow = SolvedType::TagUnion( + vec![(TagName::Global("Overflow".into()), vec![])], + Box::new(SolvedType::Wildcard), + ); + + add_type(Symbol::NUM_ADD_CHECKED, { + let_tvars! { u, v, w, num, result, star }; + unique_function( + vec![num_type(u, num), num_type(v, num)], + result_type(result, num_type(w, num), lift(star, overflow)), + ) + }); + + // addWrap : Int, Int -> Int + add_type(Symbol::NUM_ADD_WRAP, { + let_tvars! { u, v, w }; + unique_function(vec![int_type(u), int_type(v)], int_type(w)) + }); + // sub or (-) : Num a, Num a -> Num a add_type(Symbol::NUM_SUB, { let_tvars! { u, v, w, num }; @@ -475,6 +495,12 @@ pub fn types() -> MutMap { unique_function(vec![float_type(star1)], int_type(star2)) }); + // atan : Float -> Float + add_type(Symbol::NUM_ATAN, { + let_tvars! { star1, star2 }; + unique_function(vec![float_type(star1)], float_type(star2)) + }); + // Bool module // isEq or (==) : Attr * a, Attr * a -> Attr * Bool diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 065664ee6c..4c68bf592e 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -68,6 +68,8 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap { Symbol::LIST_KEEP_IF => list_keep_if, Symbol::LIST_WALK_RIGHT => list_walk_right, Symbol::NUM_ADD => num_add, + Symbol::NUM_ADD_CHECKED => num_add_checked, + Symbol::NUM_ADD_WRAP => num_add_wrap, Symbol::NUM_SUB => num_sub, Symbol::NUM_MUL => num_mul, Symbol::NUM_GT => num_gt, @@ -95,6 +97,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap { Symbol::NUM_CEILING => num_ceiling, Symbol::NUM_POW_INT => num_pow_int, Symbol::NUM_FLOOR => num_floor, + Symbol::NUM_ATAN => num_atan, } } @@ -238,6 +241,105 @@ fn num_add(symbol: Symbol, var_store: &mut VarStore) -> Def { num_binop(symbol, var_store, LowLevel::NumAdd) } +/// Num.add : Num a, Num a -> Num a +fn num_add_wrap(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_binop(symbol, var_store, LowLevel::NumAddWrap) +} + +/// Num.add : Num a, Num a -> Num a +fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { + let bool_var = var_store.fresh(); + let num_var_1 = var_store.fresh(); + let num_var_2 = var_store.fresh(); + let num_var_3 = var_store.fresh(); + let ret_var = var_store.fresh(); + let record_var = var_store.fresh(); + + // let arg_3 = RunLowLevel NumAddChecked arg_1 arg_2 + // + // if arg_3.b then + // # overflow + // Err Overflow + // else + // # all is well + // Ok arg_3.a + + let cont = If { + branch_var: ret_var, + cond_var: bool_var, + branches: vec![( + // if-condition + no_region( + // arg_3.b + Access { + record_var, + ext_var: var_store.fresh(), + field: "b".into(), + field_var: var_store.fresh(), + loc_expr: Box::new(no_region(Var(Symbol::ARG_3))), + }, + ), + // overflow! + no_region(tag( + "Err", + vec![tag("Overflow", Vec::new(), var_store)], + var_store, + )), + )], + final_else: Box::new( + // all is well + no_region( + // Ok arg_3.a + tag( + "Ok", + vec![ + // arg_3.a + Access { + record_var, + ext_var: var_store.fresh(), + field: "a".into(), + field_var: num_var_3, + loc_expr: Box::new(no_region(Var(Symbol::ARG_3))), + }, + ], + var_store, + ), + ), + ), + }; + + // arg_3 = RunLowLevel NumAddChecked arg_1 arg_2 + let def = crate::def::Def { + loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_3)), + loc_expr: no_region(RunLowLevel { + op: LowLevel::NumAddChecked, + args: vec![ + (num_var_1, Var(Symbol::ARG_1)), + (num_var_2, Var(Symbol::ARG_2)), + ], + ret_var: record_var, + }), + expr_var: record_var, + pattern_vars: SendMap::default(), + annotation: None, + }; + + let body = LetNonRec( + Box::new(def), + Box::new(no_region(cont)), + ret_var, + SendMap::default(), + ); + + defn( + symbol, + vec![(num_var_1, Symbol::ARG_1), (num_var_2, Symbol::ARG_2)], + var_store, + body, + ret_var, + ) +} + /// Num.sub : Num a, Num a -> Num a fn num_sub(symbol: Symbol, var_store: &mut VarStore) -> Def { num_binop(symbol, var_store, LowLevel::NumSub) @@ -664,6 +766,26 @@ fn num_floor(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// Num.atan : Float -> Float +fn num_atan(symbol: Symbol, var_store: &mut VarStore) -> Def { + let arg_float_var = var_store.fresh(); + let ret_float_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::NumAtan, + args: vec![(arg_float_var, Var(Symbol::ARG_1))], + ret_var: ret_float_var, + }; + + defn( + symbol, + vec![(arg_float_var, Symbol::ARG_1)], + var_store, + body, + ret_float_var, + ) +} + /// List.isEmpty : List * -> Bool fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); diff --git a/compiler/gen/Cargo.toml b/compiler/gen/Cargo.toml index 095894228d..ffda052fc9 100644 --- a/compiler/gen/Cargo.toml +++ b/compiler/gen/Cargo.toml @@ -38,7 +38,7 @@ inlinable_string = "0.1" # commit of TheDan64/inkwell, push a new tag which points to the latest commit, # change the tag value in this Cargo.toml to point to that tag, and `cargo update`. # This way, GitHub Actions works and nobody's builds get broken. -inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release1" } +inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release2" } target-lexicon = "0.10" [dev-dependencies] diff --git a/compiler/gen/src/lib.rs b/compiler/gen/src/lib.rs index f053e8771d..b7a0724fd7 100644 --- a/compiler/gen/src/lib.rs +++ b/compiler/gen/src/lib.rs @@ -13,3 +13,5 @@ pub mod layout_id; pub mod llvm; + +pub mod run_roc; diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 379bb623f1..de0b7a18b2 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -270,6 +270,12 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { LLVM_FLOOR_F64, f64_type.fn_type(&[f64_type.into()], false), ); + + add_intrinsic(module, LLVM_SADD_WITH_OVERFLOW_I64, { + let fields = [i64_type.into(), i1_type.into()]; + ctx.struct_type(&fields, false) + .fn_type(&[i64_type.into(), i64_type.into()], false) + }); } static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64"; @@ -282,6 +288,7 @@ static LLVM_COS_F64: &str = "llvm.cos.f64"; static LLVM_POW_F64: &str = "llvm.pow.f64"; static LLVM_CEILING_F64: &str = "llvm.ceil.f64"; static LLVM_FLOOR_F64: &str = "llvm.floor.f64"; +static LLVM_SADD_WITH_OVERFLOW_I64: &str = "llvm.sadd.with.overflow.i64"; fn add_intrinsic<'ctx>( module: &Module<'ctx>, @@ -353,7 +360,10 @@ enum PassVia { impl PassVia { fn from_layout(ptr_bytes: u32, layout: &Layout<'_>) -> Self { - if layout.stack_size(ptr_bytes) > 16 { + let stack_size = layout.stack_size(ptr_bytes); + let eightbyte = 8; + + if stack_size > 2 * eightbyte { PassVia::Memory } else { PassVia::Register @@ -361,14 +371,14 @@ impl PassVia { } } -pub fn make_main_function<'a, 'ctx, 'env>( +/// entry point to roc code; uses the fastcc calling convention +pub fn build_roc_main<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, layout: &Layout<'a>, main_body: &roc_mono::ir::Stmt<'a>, -) -> (&'static str, &'a FunctionValue<'ctx>) { +) -> &'a FunctionValue<'ctx> { use inkwell::types::BasicType; - use PassVia::*; let context = env.context; let builder = env.builder; @@ -386,7 +396,7 @@ pub fn make_main_function<'a, 'ctx, 'env>( .module .add_function(roc_main_fn_name, roc_main_fn_type, None); - // our exposed main function adheres to the C calling convention + // internal function, use fast calling convention roc_main_fn.set_call_conventions(FAST_CALL_CONV); // Add main's body @@ -403,17 +413,42 @@ pub fn make_main_function<'a, 'ctx, 'env>( main_body, ); + env.arena.alloc(roc_main_fn) +} + +pub fn make_main_function<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + layout: &Layout<'a>, + main_body: &roc_mono::ir::Stmt<'a>, +) -> (&'static str, &'a FunctionValue<'ctx>) { + use inkwell::types::BasicType; + use PassVia::*; + + let context = env.context; + let builder = env.builder; + + let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); + + // internal main function + let roc_main_fn = *build_roc_main(env, layout_ids, layout, main_body); + // build the C calling convention wrapper let main_fn_name = "$Test.main"; - let register_or_memory = PassVia::from_layout(env.ptr_bytes, layout); + + let fields = [Layout::Builtin(Builtin::Int64), layout.clone()]; + let main_return_layout = Layout::Struct(&fields); + let main_return_type = block_of_memory(context, &main_return_layout, env.ptr_bytes); + + let register_or_memory = PassVia::from_layout(env.ptr_bytes, &main_return_layout); let main_fn_type = match register_or_memory { Memory => { let return_value_ptr = context.i64_type().ptr_type(AddressSpace::Generic).into(); context.void_type().fn_type(&[return_value_ptr], false) } - Register => return_type.fn_type(&[], false), + Register => main_return_type.fn_type(&[], false), }; // Add main to the module. @@ -424,35 +459,157 @@ pub fn make_main_function<'a, 'ctx, 'env>( // Add main's body let basic_block = context.append_basic_block(main_fn, "entry"); + let then_block = context.append_basic_block(main_fn, "then_block"); + let catch_block = context.append_basic_block(main_fn, "catch_block"); + let cont_block = context.append_basic_block(main_fn, "cont_block"); builder.position_at_end(basic_block); - let call = builder.build_call(roc_main_fn, &[], "call_roc_main"); - call.set_call_convention(FAST_CALL_CONV); + let result_alloca = builder.build_alloca(main_return_type, "result"); - let call_result = call.try_as_basic_value().left().unwrap(); + // invoke instead of call, so that we can catch any exeptions thrown in Roc code + let call_result = { + let call = builder.build_invoke(roc_main_fn, &[], then_block, catch_block, "call_roc_main"); + call.set_call_convention(FAST_CALL_CONV); + call.try_as_basic_value().left().unwrap() + }; - match register_or_memory { - Memory => { - // write the result into the supplied pointer - // this is a void function, therefore return None - let ptr_return_type = return_type.ptr_type(AddressSpace::Generic); + // exception handling + { + builder.position_at_end(catch_block); - let ptr_as_int = main_fn.get_first_param().unwrap(); + let landing_pad_type = { + let exception_ptr = context.i8_type().ptr_type(AddressSpace::Generic).into(); + let selector_value = context.i32_type().into(); - let ptr = builder.build_bitcast(ptr_as_int, ptr_return_type, "caller_ptr"); + context.struct_type(&[exception_ptr, selector_value], false) + }; - builder.build_store(ptr.into_pointer_value(), call_result); + let info = builder + .build_catch_all_landing_pad( + &landing_pad_type, + &BasicValueEnum::IntValue(context.i8_type().const_zero()), + context.i8_type().ptr_type(AddressSpace::Generic), + "main_landing_pad", + ) + .into_struct_value(); - builder.build_return(None); - } - Register => { - // construct a normal return - // values are passed to the caller via registers - builder.build_return(Some(&call_result)); + let exception_ptr = builder + .build_extract_value(info, 0, "exception_ptr") + .unwrap(); + + let thrown = cxa_begin_catch(env, exception_ptr); + + let error_msg = { + let exception_type = u8_ptr; + let ptr = builder.build_bitcast( + thrown, + exception_type.ptr_type(AddressSpace::Generic), + "cast", + ); + + builder.build_load(ptr.into_pointer_value(), "error_msg") + }; + + let return_type = context.struct_type(&[context.i64_type().into(), u8_ptr.into()], false); + + let return_value = { + let v1 = return_type.const_zero(); + + // flag is non-zero, indicating failure + let flag = context.i64_type().const_int(1, false); + + let v2 = builder + .build_insert_value(v1, flag, 0, "set_error") + .unwrap(); + + let v3 = builder + .build_insert_value(v2, error_msg, 1, "set_exception") + .unwrap(); + + v3 + }; + + // bitcast result alloca so we can store our concrete type { flag, error_msg } in there + let result_alloca_bitcast = builder + .build_bitcast( + result_alloca, + return_type.ptr_type(AddressSpace::Generic), + "result_alloca_bitcast", + ) + .into_pointer_value(); + + // store our return value + builder.build_store(result_alloca_bitcast, return_value); + + cxa_end_catch(env); + + builder.build_unconditional_branch(cont_block); + } + + { + builder.position_at_end(then_block); + + let actual_return_type = + basic_type_from_layout(env.arena, env.context, layout, env.ptr_bytes); + let return_type = + context.struct_type(&[context.i64_type().into(), actual_return_type], false); + + let return_value = { + let v1 = return_type.const_zero(); + + let v2 = builder + .build_insert_value(v1, context.i64_type().const_zero(), 0, "set_no_error") + .unwrap(); + let v3 = builder + .build_insert_value(v2, call_result, 1, "set_call_result") + .unwrap(); + + v3 + }; + + let ptr = builder.build_bitcast( + result_alloca, + return_type.ptr_type(AddressSpace::Generic), + "name", + ); + builder.build_store(ptr.into_pointer_value(), return_value); + + builder.build_unconditional_branch(cont_block); + } + + { + builder.position_at_end(cont_block); + + let result = builder.build_load(result_alloca, "result"); + + match register_or_memory { + Memory => { + // write the result into the supplied pointer + let ptr_return_type = main_return_type.ptr_type(AddressSpace::Generic); + + let ptr_as_int = main_fn.get_first_param().unwrap(); + + let ptr = builder.build_bitcast(ptr_as_int, ptr_return_type, "caller_ptr"); + + builder.build_store(ptr.into_pointer_value(), result); + + // this is a void function, therefore return None + builder.build_return(None); + } + Register => { + // construct a normal return + // values are passed to the caller via registers + builder.build_return(Some(&result)); + } } } + // MUST set the personality at the very end; + // doing it earlier can cause the personality to be ignored + let personality_func = get_gxx_personality_v0(env); + main_fn.set_personality_function(personality_func); + (main_fn_name, env.arena.alloc(main_fn)) } @@ -790,8 +947,6 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( debug_assert!(*union_size > 1); let ptr_size = env.ptr_bytes; - let mut filler = tag_layout.stack_size(ptr_size); - let ctx = env.context; let builder = env.builder; @@ -828,16 +983,9 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( } else { field_vals.push(val); } - - filler -= field_size; } } - // TODO verify that this is required (better safe than sorry) - if filler > 0 { - field_types.push(env.context.i8_type().array_type(filler).into()); - } - // Create the struct_type let struct_type = ctx.struct_type(field_types.into_bump_slice(), false); let mut struct_val = struct_type.const_zero().into(); @@ -1385,7 +1533,14 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( build_exp_stmt(env, layout_ids, scope, parent, cont) } - _ => todo!("unsupported expr {:?}", stmt), + + RuntimeError(error_msg) => { + throw_exception(env, error_msg); + + // unused value (must return a BasicValue) + let zero = env.context.i64_type().const_zero(); + zero.into() + } } } @@ -1951,7 +2106,7 @@ fn run_low_level<'a, 'ctx, 'env>( list_join(env, inplace, parent, list, outer_list_layout) } NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumCeiling | NumFloor - | NumToFloat => { + | NumToFloat | NumIsFinite | NumAtan => { debug_assert_eq!(args.len(), 1); let (arg, arg_layout) = load_symbol_and_layout(env, scope, &args[0]); @@ -2062,7 +2217,7 @@ fn run_low_level<'a, 'ctx, 'env>( } NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked - | NumDivUnchecked | NumPow | NumPowInt => { + | NumAddWrap | NumAddChecked | NumDivUnchecked | NumPow | NumPowInt => { debug_assert_eq!(args.len(), 2); let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]); @@ -2077,6 +2232,7 @@ fn run_low_level<'a, 'ctx, 'env>( match lhs_builtin { Int128 | Int64 | Int32 | Int16 | Int8 => build_int_binop( env, + parent, lhs_arg.into_int_value(), lhs_layout, rhs_arg.into_int_value(), @@ -2085,6 +2241,7 @@ fn run_low_level<'a, 'ctx, 'env>( ), Float128 | Float64 | Float32 | Float16 => build_float_binop( env, + parent, lhs_arg.into_float_value(), lhs_layout, rhs_arg.into_float_value(), @@ -2256,6 +2413,7 @@ where fn build_int_binop<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, + parent: FunctionValue<'ctx>, lhs: IntValue<'ctx>, _lhs_layout: &Layout<'a>, rhs: IntValue<'ctx>, @@ -2268,7 +2426,37 @@ fn build_int_binop<'a, 'ctx, 'env>( let bd = env.builder; match op { - NumAdd => bd.build_int_add(lhs, rhs, "add_int").into(), + NumAdd => { + let context = env.context; + let result = env + .call_intrinsic(LLVM_SADD_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]) + .into_struct_value(); + + let add_result = bd.build_extract_value(result, 0, "add_result").unwrap(); + let has_overflowed = bd.build_extract_value(result, 1, "has_overflowed").unwrap(); + + let condition = bd.build_int_compare( + IntPredicate::EQ, + has_overflowed.into_int_value(), + context.bool_type().const_zero(), + "has_not_overflowed", + ); + + let then_block = context.append_basic_block(parent, "then_block"); + let throw_block = context.append_basic_block(parent, "throw_block"); + + bd.build_conditional_branch(condition, then_block, throw_block); + + bd.position_at_end(throw_block); + + throw_exception(env, "integer addition overflowed!"); + + bd.position_at_end(then_block); + + add_result + } + NumAddWrap => bd.build_int_add(lhs, rhs, "add_int_wrap").into(), + NumAddChecked => env.call_intrinsic(LLVM_SADD_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]), NumSub => bd.build_int_sub(lhs, rhs, "sub_int").into(), NumMul => bd.build_int_mul(lhs, rhs, "mul_int").into(), NumGt => bd.build_int_compare(SGT, lhs, rhs, "int_gt").into(), @@ -2305,6 +2493,7 @@ fn call_bitcode_fn<'a, 'ctx, 'env>( fn build_float_binop<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, + parent: FunctionValue<'ctx>, lhs: FloatValue<'ctx>, _lhs_layout: &Layout<'a>, rhs: FloatValue<'ctx>, @@ -2317,7 +2506,55 @@ fn build_float_binop<'a, 'ctx, 'env>( let bd = env.builder; match op { - NumAdd => bd.build_float_add(lhs, rhs, "add_float").into(), + NumAdd => { + let builder = env.builder; + let context = env.context; + + let result = bd.build_float_add(lhs, rhs, "add_float"); + + let is_finite = + call_bitcode_fn(NumIsFinite, env, &[result.into()], "is_finite_").into_int_value(); + + let then_block = context.append_basic_block(parent, "then_block"); + let throw_block = context.append_basic_block(parent, "throw_block"); + + builder.build_conditional_branch(is_finite, then_block, throw_block); + + builder.position_at_end(throw_block); + + throw_exception(env, "float addition overflowed!"); + + builder.position_at_end(then_block); + + result.into() + } + NumAddChecked => { + let context = env.context; + + let result = bd.build_float_add(lhs, rhs, "add_float"); + + let is_finite = + call_bitcode_fn(NumIsFinite, env, &[result.into()], "is_finite_").into_int_value(); + let is_infinite = bd.build_not(is_finite, "negate"); + + let struct_type = context.struct_type( + &[context.f64_type().into(), context.bool_type().into()], + false, + ); + + let struct_value = { + let v1 = struct_type.const_zero(); + let v2 = bd.build_insert_value(v1, result, 0, "set_result").unwrap(); + let v3 = bd + .build_insert_value(v2, is_infinite, 1, "set_is_infinite") + .unwrap(); + + v3.into_struct_value() + }; + + struct_value.into() + } + NumAddWrap => unreachable!("wrapping addition is not defined on floats"), NumSub => bd.build_float_sub(lhs, rhs, "sub_float").into(), NumMul => bd.build_float_mul(lhs, rhs, "mul_float").into(), NumGt => bd.build_float_compare(OGT, lhs, rhs, "float_gt").into(), @@ -2421,8 +2658,261 @@ fn build_float_unary_op<'a, 'ctx, 'env>( env.context.i64_type(), "num_floor", ), + NumIsFinite => call_bitcode_fn(NumIsFinite, env, &[arg.into()], "is_finite_"), + NumAtan => call_bitcode_fn(NumAtan, env, &[arg.into()], "atan_"), _ => { unreachable!("Unrecognized int unary operation: {:?}", op); } } } + +fn define_global_str<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + message: &str, +) -> inkwell::values::GlobalValue<'ctx> { + let module = env.module; + + // hash the name so we don't re-define existing messages + let name = { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let mut hasher = DefaultHasher::new(); + message.hash(&mut hasher); + let hash = hasher.finish(); + + format!("_Error_message_{}", hash) + }; + + match module.get_global(&name) { + Some(current) => current, + None => unsafe { env.builder.build_global_string(message, name.as_str()) }, + } +} + +fn throw_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, message: &str) { + let context = env.context; + let builder = env.builder; + + let info = { + // we represend both void and char pointers with `u8*` + let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); + + // allocate an exception (that can hold a pointer to a string) + let str_ptr_size = env + .context + .i64_type() + .const_int(env.ptr_bytes as u64, false); + let initial = cxa_allocate_exception(env, str_ptr_size); + + // define the error message as a global + // (a hash is used such that the same value is not defined repeatedly) + let error_msg_global = define_global_str(env, message); + + // cast this to a void pointer + let error_msg_ptr = + builder.build_bitcast(error_msg_global.as_pointer_value(), u8_ptr, "unused"); + + // store this void pointer in the exception + let exception_type = u8_ptr; + let exception_value = error_msg_ptr; + + let temp = builder + .build_bitcast( + initial, + exception_type.ptr_type(AddressSpace::Generic), + "exception_object_str_ptr_ptr", + ) + .into_pointer_value(); + + builder.build_store(temp, exception_value); + + initial + }; + + cxa_throw_exception(env, info); + + builder.build_unreachable(); +} + +fn cxa_allocate_exception<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + exception_size: IntValue<'ctx>, +) -> BasicValueEnum<'ctx> { + let name = "__cxa_allocate_exception"; + + let module = env.module; + let context = env.context; + let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); + + let function = match module.get_function(&name) { + Some(gvalue) => gvalue, + None => { + // void *__cxa_allocate_exception(size_t thrown_size); + let cxa_allocate_exception = module.add_function( + name, + u8_ptr.fn_type(&[context.i64_type().into()], false), + Some(Linkage::External), + ); + cxa_allocate_exception.set_call_conventions(C_CALL_CONV); + + cxa_allocate_exception + } + }; + let call = env.builder.build_call( + function, + &[exception_size.into()], + "exception_object_void_ptr", + ); + + call.set_call_convention(C_CALL_CONV); + call.try_as_basic_value().left().unwrap() +} + +fn cxa_throw_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, info: BasicValueEnum<'ctx>) { + let name = "__cxa_throw"; + + let module = env.module; + let context = env.context; + let builder = env.builder; + + let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); + + let function = match module.get_function(&name) { + Some(value) => value, + None => { + // void __cxa_throw (void *thrown_exception, std::type_info *tinfo, void (*dest) (void *) ); + let cxa_throw = module.add_function( + name, + context + .void_type() + .fn_type(&[u8_ptr.into(), u8_ptr.into(), u8_ptr.into()], false), + Some(Linkage::External), + ); + cxa_throw.set_call_conventions(C_CALL_CONV); + + cxa_throw + } + }; + + // global storing the type info of a c++ int (equivalent to `i32` in llvm) + // we just need any valid such value, and arbitrarily use this one + let ztii = match module.get_global("_ZTIi") { + Some(gvalue) => gvalue.as_pointer_value(), + None => { + let ztii = module.add_global(u8_ptr, Some(AddressSpace::Generic), "_ZTIi"); + ztii.set_linkage(Linkage::External); + + ztii.as_pointer_value() + } + }; + + let type_info = builder.build_bitcast(ztii, u8_ptr, "cast"); + let null: BasicValueEnum = u8_ptr.const_zero().into(); + + let call = builder.build_call(function, &[info, type_info, null], "throw"); + call.set_call_convention(C_CALL_CONV); +} + +#[allow(dead_code)] +fn cxa_rethrow_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> { + let name = "__cxa_rethrow"; + + let module = env.module; + let context = env.context; + + let function = match module.get_function(&name) { + Some(gvalue) => gvalue, + None => { + let cxa_rethrow = module.add_function( + name, + context.void_type().fn_type(&[], false), + Some(Linkage::External), + ); + cxa_rethrow.set_call_conventions(C_CALL_CONV); + + cxa_rethrow + } + }; + let call = env.builder.build_call(function, &[], "never_used"); + + call.set_call_convention(C_CALL_CONV); + call.try_as_basic_value().left().unwrap() +} + +fn get_gxx_personality_v0<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> FunctionValue<'ctx> { + let name = "__cxa_rethrow"; + + let module = env.module; + let context = env.context; + + match module.get_function(&name) { + Some(gvalue) => gvalue, + None => { + let personality_func = module.add_function( + "__gxx_personality_v0", + context.i64_type().fn_type(&[], false), + Some(Linkage::External), + ); + personality_func.set_call_conventions(C_CALL_CONV); + + personality_func + } + } +} + +fn cxa_end_catch<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) { + let name = "__cxa_end_catch"; + + let module = env.module; + let context = env.context; + + let function = match module.get_function(&name) { + Some(gvalue) => gvalue, + None => { + let cxa_end_catch = module.add_function( + name, + context.void_type().fn_type(&[], false), + Some(Linkage::External), + ); + cxa_end_catch.set_call_conventions(C_CALL_CONV); + + cxa_end_catch + } + }; + let call = env.builder.build_call(function, &[], "never_used"); + + call.set_call_convention(C_CALL_CONV); +} + +fn cxa_begin_catch<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + exception_ptr: BasicValueEnum<'ctx>, +) -> BasicValueEnum<'ctx> { + let name = "__cxa_begin_catch"; + + let module = env.module; + let context = env.context; + + let function = match module.get_function(&name) { + Some(gvalue) => gvalue, + None => { + let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); + + let cxa_begin_catch = module.add_function( + "__cxa_begin_catch", + u8_ptr.fn_type(&[u8_ptr.into()], false), + Some(Linkage::External), + ); + cxa_begin_catch.set_call_conventions(C_CALL_CONV); + + cxa_begin_catch + } + }; + let call = env + .builder + .build_call(function, &[exception_ptr], "exception_payload_ptr"); + + call.set_call_convention(C_CALL_CONV); + call.try_as_basic_value().left().unwrap() +} diff --git a/compiler/gen/src/llvm/build_list.rs b/compiler/gen/src/llvm/build_list.rs index 078ae8bd64..25dc8fe0e5 100644 --- a/compiler/gen/src/llvm/build_list.rs +++ b/compiler/gen/src/llvm/build_list.rs @@ -1570,6 +1570,26 @@ pub fn list_is_not_empty<'ctx>(env: &Env<'_, 'ctx, '_>, len: IntValue<'ctx>) -> ) } +pub fn load_list<'ctx>( + builder: &Builder<'ctx>, + wrapper_struct: StructValue<'ctx>, + ptr_type: PointerType<'ctx>, +) -> (IntValue<'ctx>, PointerValue<'ctx>) { + let ptr_as_int = builder + .build_extract_value(wrapper_struct, Builtin::WRAPPER_PTR, "read_list_ptr") + .unwrap() + .into_int_value(); + + let ptr = builder.build_int_to_ptr(ptr_as_int, ptr_type, "list_cast_ptr"); + + let length = builder + .build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "list_len") + .unwrap() + .into_int_value(); + + (length, ptr) +} + pub fn load_list_ptr<'ctx>( builder: &Builder<'ctx>, wrapper_struct: StructValue<'ctx>, diff --git a/compiler/gen/src/llvm/builtins.bc b/compiler/gen/src/llvm/builtins.bc index c56125a48e..9d937909bb 100644 Binary files a/compiler/gen/src/llvm/builtins.bc and b/compiler/gen/src/llvm/builtins.bc differ diff --git a/compiler/gen/src/llvm/refcounting.rs b/compiler/gen/src/llvm/refcounting.rs index d7d9113d4e..a76f56f069 100644 --- a/compiler/gen/src/llvm/refcounting.rs +++ b/compiler/gen/src/llvm/refcounting.rs @@ -100,7 +100,7 @@ pub fn decrement_refcount_layout<'a, 'ctx, 'env>( #[inline(always)] fn decrement_refcount_builtin<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, - _parent: FunctionValue<'ctx>, + parent: FunctionValue<'ctx>, layout_ids: &mut LayoutIds<'a>, value: BasicValueEnum<'ctx>, layout: &Layout<'a>, @@ -109,14 +109,37 @@ fn decrement_refcount_builtin<'a, 'ctx, 'env>( use Builtin::*; match builtin { - List(MemoryMode::Refcounted, element_layout) => { + List(memory_mode, element_layout) => { + let wrapper_struct = value.into_struct_value(); if element_layout.contains_refcounted() { - // TODO decrement all values + use crate::llvm::build_list::{incrementing_elem_loop, load_list}; + use inkwell::types::BasicType; + + let ptr_type = + basic_type_from_layout(env.arena, env.context, element_layout, env.ptr_bytes) + .ptr_type(AddressSpace::Generic); + + let (len, ptr) = load_list(env.builder, wrapper_struct, ptr_type); + + let loop_fn = |_index, element| { + decrement_refcount_layout(env, parent, layout_ids, element, element_layout); + }; + + incrementing_elem_loop( + env.builder, + env.context, + parent, + ptr, + len, + "dec_index", + loop_fn, + ); } - build_dec_list(env, layout_ids, layout, value.into_struct_value()); - } - List(MemoryMode::Unique, _element_layout) => { - // do nothing + + if let MemoryMode::Refcounted = memory_mode { + build_inc_list(env, layout_ids, layout, wrapper_struct); + } + build_dec_list(env, layout_ids, layout, wrapper_struct); } Set(element_layout) => { if element_layout.contains_refcounted() { @@ -158,7 +181,7 @@ pub fn increment_refcount_layout<'a, 'ctx, 'env>( #[inline(always)] fn increment_refcount_builtin<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, - _parent: FunctionValue<'ctx>, + parent: FunctionValue<'ctx>, layout_ids: &mut LayoutIds<'a>, value: BasicValueEnum<'ctx>, layout: &Layout<'a>, @@ -167,15 +190,36 @@ fn increment_refcount_builtin<'a, 'ctx, 'env>( use Builtin::*; match builtin { - List(MemoryMode::Refcounted, element_layout) => { - if element_layout.contains_refcounted() { - // TODO decrement all values - } + List(memory_mode, element_layout) => { let wrapper_struct = value.into_struct_value(); - build_inc_list(env, layout_ids, layout, wrapper_struct); - } - List(MemoryMode::Unique, _element_layout) => { - // do nothing + if element_layout.contains_refcounted() { + use crate::llvm::build_list::{incrementing_elem_loop, load_list}; + use inkwell::types::BasicType; + + let ptr_type = + basic_type_from_layout(env.arena, env.context, element_layout, env.ptr_bytes) + .ptr_type(AddressSpace::Generic); + + let (len, ptr) = load_list(env.builder, wrapper_struct, ptr_type); + + let loop_fn = |_index, element| { + increment_refcount_layout(env, parent, layout_ids, element, element_layout); + }; + + incrementing_elem_loop( + env.builder, + env.context, + parent, + ptr, + len, + "inc_index", + loop_fn, + ); + } + + if let MemoryMode::Refcounted = memory_mode { + build_inc_list(env, layout_ids, layout, wrapper_struct); + } } Set(element_layout) => { if element_layout.contains_refcounted() { diff --git a/compiler/gen/src/run_roc.rs b/compiler/gen/src/run_roc.rs new file mode 100644 index 0000000000..fd93036d48 --- /dev/null +++ b/compiler/gen/src/run_roc.rs @@ -0,0 +1,124 @@ +use std::ffi::CString; +use std::os::raw::c_char; + +#[repr(C)] +union Payload { + success: T, + failure: *mut c_char, +} + +#[repr(C)] +pub struct RocCallResult { + pub flag: u64, + payload: Payload, +} + +impl Into> for RocCallResult { + fn into(self) -> Result { + if self.flag == 0 { + Ok(unsafe { self.payload.success }) + } else { + Err(unsafe { + let raw = CString::from_raw(self.payload.failure); + + let result = format!("{:?}", raw); + + // make sure rust does not try to free the Roc string + std::mem::forget(raw); + + result + }) + } + } +} + +#[macro_export] +macro_rules! run_jit_function { + ($execution_engine: expr, $main_fn_name: expr, $ty:ty, $transform:expr) => {{ + let v: std::vec::Vec = std::vec::Vec::new(); + run_jit_function!($execution_engine, $main_fn_name, $ty, $transform, v) + }}; + + ($execution_engine: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr) => {{ + use inkwell::context::Context; + use inkwell::execution_engine::JitFunction; + use roc_gen::run_roc::RocCallResult; + + unsafe { + let main: JitFunction RocCallResult<$ty>> = $execution_engine + .get_function($main_fn_name) + .ok() + .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) + .expect("errored"); + + match main.call().into() { + Ok(success) => { + // only if there are no exceptions thrown, check for errors + assert_eq!( + $errors, + std::vec::Vec::new(), + "Encountered errors: {:?}", + $errors + ); + + $transform(success) + } + Err(error_msg) => panic!("Roc failed with message: {}", error_msg), + } + } + }}; +} + +/// In the repl, we don't know the type that is returned; it it's large enough to not fit in 2 +/// registers (i.e. size bigger than 16 bytes on 64-bit systems), then we use this macro. +/// It explicitly allocates a buffer that the roc main function can write its result into. +#[macro_export] +macro_rules! run_jit_function_dynamic_type { + ($execution_engine: expr, $main_fn_name: expr, $bytes:expr, $transform:expr) => {{ + let v: std::vec::Vec = std::vec::Vec::new(); + run_jit_function_dynamic_type!($execution_engine, $main_fn_name, $bytes, $transform, v) + }}; + + ($execution_engine: expr, $main_fn_name: expr, $bytes:expr, $transform:expr, $errors:expr) => {{ + use inkwell::context::Context; + use inkwell::execution_engine::JitFunction; + use roc_gen::run_roc::RocCallResult; + + unsafe { + let main: JitFunction = $execution_engine + .get_function($main_fn_name) + .ok() + .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) + .expect("errored"); + + let layout = std::alloc::Layout::array::($bytes).unwrap(); + let result = std::alloc::alloc(layout); + main.call(result); + + let flag = *result; + + if flag == 0 { + $transform(result.offset(8) as *const u8) + } else { + use std::ffi::CString; + use std::os::raw::c_char; + + // first field is a char pointer (to the error message) + // read value, and transmute to a pointer + let ptr_as_int = *(result as *const u64).offset(1); + let ptr = std::mem::transmute::(ptr_as_int); + + // make CString (null-terminated) + let raw = CString::from_raw(ptr); + + let result = format!("{:?}", raw); + + // make sure rust doesn't try to free the Roc constant string + std::mem::forget(raw); + + eprintln!("{}", result); + panic!("Roc hit an error"); + } + } + }}; +} diff --git a/compiler/gen/tests/gen_list.rs b/compiler/gen/tests/gen_list.rs index d532351341..ff4d7f7edd 100644 --- a/compiler/gen/tests/gen_list.rs +++ b/compiler/gen/tests/gen_list.rs @@ -663,12 +663,14 @@ mod gen_list { #[test] fn list_concat_large() { - // these values produce mono ASTs so large that - // it can cause a stack overflow. This has been solved - // for current code, but may become a problem again in the future. - assert_concat_worked(150, 150); - assert_concat_worked(129, 350); - assert_concat_worked(350, 129); + with_larger_debug_stack(|| { + // these values produce mono ASTs so large that + // it can cause a stack overflow. This has been solved + // for current code, but may become a problem again in the future. + assert_concat_worked(150, 150); + assert_concat_worked(129, 350); + assert_concat_worked(350, 129); + }) } #[test] diff --git a/compiler/gen/tests/gen_num.rs b/compiler/gen/tests/gen_num.rs index 1c9e878e1a..16129a03f3 100644 --- a/compiler/gen/tests/gen_num.rs +++ b/compiler/gen/tests/gen_num.rs @@ -685,4 +685,107 @@ mod gen_num { fn pow_int() { assert_evals_to!("Num.powInt 2 3", 8, i64); } + + #[test] + fn atan() { + assert_evals_to!("Num.atan 10", 1.4711276743037347, f64); + } + + #[test] + #[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)] + fn int_overflow() { + assert_evals_to!( + indoc!( + r#" + 9_223_372_036_854_775_807 + 1 + "# + ), + 0, + i64 + ); + } + + #[test] + fn int_add_checked() { + assert_evals_to!( + indoc!( + r#" + when Num.addChecked 1 2 is + Ok v -> v + _ -> -1 + "# + ), + 3, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + when Num.addChecked 9_223_372_036_854_775_807 1 is + Err Overflow -> -1 + Ok v -> v + "# + ), + -1, + i64 + ); + } + + #[test] + fn int_add_wrap() { + assert_evals_to!( + indoc!( + r#" + Num.addWrap 9_223_372_036_854_775_807 1 + "# + ), + std::i64::MIN, + i64 + ); + } + + #[test] + fn float_add_checked_pass() { + assert_evals_to!( + indoc!( + r#" + when Num.addChecked 1.0 0.0 is + Ok v -> v + Err Overflow -> -1.0 + "# + ), + 1.0, + f64 + ); + } + + #[test] + fn float_add_checked_fail() { + assert_evals_to!( + indoc!( + r#" + when Num.addChecked 1.7976931348623157e308 1.7976931348623157e308 is + Err Overflow -> -1 + Ok v -> v + "# + ), + -1.0, + f64 + ); + } + + #[test] + #[should_panic(expected = r#"Roc failed with message: "float addition overflowed!"#)] + fn float_overflow() { + assert_evals_to!( + indoc!( + r#" + 1.7976931348623157e308 + 1.7976931348623157e308 + "# + ), + 0.0, + f64 + ); + } } diff --git a/compiler/gen/tests/gen_primitives.rs b/compiler/gen/tests/gen_primitives.rs index 00b72483ed..455bc504c2 100644 --- a/compiler/gen/tests/gen_primitives.rs +++ b/compiler/gen/tests/gen_primitives.rs @@ -806,4 +806,21 @@ mod gen_primitives { i64 ); } + + #[test] + #[should_panic(expected = "Roc failed with message: ")] + fn exception() { + assert_evals_to!( + indoc!( + r#" + if True then + x + z + else + y + z + "# + ), + 3, + i64 + ); + } } diff --git a/compiler/gen/tests/gen_records.rs b/compiler/gen/tests/gen_records.rs index 0319e09f17..ffcb65cca3 100644 --- a/compiler/gen/tests/gen_records.rs +++ b/compiler/gen/tests/gen_records.rs @@ -664,6 +664,19 @@ mod gen_records { ); } + #[test] + fn return_nested_record() { + assert_evals_to!( + indoc!( + r#" + { flag: 0x0, payload: { a: 6.28, b: 3.14, c: 0.1 } } + "# + ), + (0x0, (6.28, 3.14, 0.1)), + (i64, (f64, f64, f64)) + ); + } + #[test] fn just_to_be_sure() { assert_evals_to!( diff --git a/compiler/gen/tests/helpers/eval.rs b/compiler/gen/tests/helpers/eval.rs index 4cd452c5b0..2a556a3119 100644 --- a/compiler/gen/tests/helpers/eval.rs +++ b/compiler/gen/tests/helpers/eval.rs @@ -6,7 +6,11 @@ pub fn helper_without_uniqueness<'a>( src: &str, leak: bool, context: &'a inkwell::context::Context, -) -> (&'static str, inkwell::execution_engine::ExecutionEngine<'a>) { +) -> ( + &'static str, + Vec, + inkwell::execution_engine::ExecutionEngine<'a>, +) { use crate::helpers::{can_expr, infer_expr, CanExprOut}; use inkwell::OptimizationLevel; use roc_gen::llvm::build::{build_proc, build_proc_header}; @@ -24,6 +28,8 @@ pub fn helper_without_uniqueness<'a>( problems, .. } = can_expr(src); + + // don't panic based on the errors here, so we can test that RuntimeError generates the correct code let errors = problems .into_iter() .filter(|problem| { @@ -37,8 +43,6 @@ pub fn helper_without_uniqueness<'a>( }) .collect::>(); - assert_eq!(errors, Vec::new(), "Encountered errors: {:?}", errors); - let subs = Subs::new(var_store.into()); let mut unify_problems = Vec::new(); let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); @@ -178,7 +182,7 @@ pub fn helper_without_uniqueness<'a>( // Uncomment this to see the module's optimized LLVM instruction output: // env.module.print_to_stderr(); - (main_fn_name, execution_engine.clone()) + (main_fn_name, errors, execution_engine.clone()) } pub fn helper_with_uniqueness<'a>( @@ -360,7 +364,7 @@ macro_rules! assert_opt_evals_to { ($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => { use bumpalo::Bump; use inkwell::context::Context; - use inkwell::execution_engine::JitFunction; + use roc_gen::run_jit_function; let arena = Bump::new(); @@ -369,15 +373,8 @@ macro_rules! assert_opt_evals_to { let (main_fn_name, execution_engine) = $crate::helpers::eval::helper_with_uniqueness(&arena, $src, $leak, &context); - unsafe { - let main: JitFunction $ty> = execution_engine - .get_function(main_fn_name) - .ok() - .ok_or(format!("Unable to JIT compile `{}`", main_fn_name)) - .expect("errored"); - - assert_eq!($transform(main.call()), $expected); - } + let transform = |success| assert_eq!($transform(success), $expected); + run_jit_function!(execution_engine, main_fn_name, $ty, transform) }; ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { @@ -390,24 +387,17 @@ macro_rules! assert_llvm_evals_to { ($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => { use bumpalo::Bump; use inkwell::context::Context; - use inkwell::execution_engine::JitFunction; + use roc_gen::run_jit_function; let arena = Bump::new(); let context = Context::create(); - let (main_fn_name, execution_engine) = + let (main_fn_name, errors, execution_engine) = $crate::helpers::eval::helper_without_uniqueness(&arena, $src, $leak, &context); - unsafe { - let main: JitFunction $ty> = execution_engine - .get_function(main_fn_name) - .ok() - .ok_or(format!("Unable to JIT compile `{}`", main_fn_name)) - .expect("errored"); - - assert_eq!($transform(main.call()), $expected); - } + let transform = |success| assert_eq!($transform(success), $expected); + run_jit_function!(execution_engine, main_fn_name, $ty, transform, errors) }; ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index e347d134c5..10f85bec53 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -20,6 +20,8 @@ pub enum LowLevel { ListKeepIf, ListWalkRight, NumAdd, + NumAddWrap, + NumAddChecked, NumSub, NumMul, NumGt, @@ -40,6 +42,8 @@ pub enum LowLevel { NumCeiling, NumPowInt, NumFloor, + NumIsFinite, + NumAtan, Eq, NotEq, And, diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index e61883ff15..b8bc4beafc 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -644,6 +644,9 @@ define_builtins! { 39 NUM_CEILING: "ceiling" 40 NUM_POW_INT: "powInt" 41 NUM_FLOOR: "floor" + 42 NUM_ADD_WRAP: "addWrap" + 43 NUM_ADD_CHECKED: "addChecked" + 44 NUM_ATAN: "atan" } 2 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index ac02b9d457..4f209d9dd3 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -521,12 +521,11 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { ListKeepIf => arena.alloc_slice_copy(&[owned, irrelevant]), ListWalkRight => arena.alloc_slice_copy(&[borrowed, irrelevant, owned]), - Eq | NotEq | And | Or | NumAdd | NumSub | NumMul | NumGt | NumGte | NumLt | NumLte - | NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt => { - arena.alloc_slice_copy(&[irrelevant, irrelevant]) - } + Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumMul | NumGt + | NumGte | NumLt | NumLte | NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow + | NumPowInt => arena.alloc_slice_copy(&[irrelevant, irrelevant]), NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor - | NumToFloat | Not => arena.alloc_slice_copy(&[irrelevant]), + | NumToFloat | Not | NumIsFinite | NumAtan => arena.alloc_slice_copy(&[irrelevant]), } } diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 34dc4fdefd..189f736d42 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -2430,6 +2430,18 @@ mod solve_expr { ); } + #[test] + fn atan() { + infer_eq_without_problem( + indoc!( + r#" + Num.atan + "# + ), + "Float -> Float", + ); + } + #[test] fn reconstruct_path() { infer_eq_without_problem( diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 8af155eade..7685810325 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -46,7 +46,7 @@ tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", # commit of TheDan64/inkwell, push a new tag which points to the latest commit, # change the tag value in this Cargo.toml to point to that tag, and `cargo update`. # This way, GitHub Actions works and nobody's builds get broken. -inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release1" } +inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release2" } target-lexicon = "0.10" winit = "0.22" wgpu = "0.5" diff --git a/editor/editor-ideas.md b/editor/editor-ideas.md index 247a5c778a..86ef1be20e 100644 --- a/editor/editor-ideas.md +++ b/editor/editor-ideas.md @@ -17,6 +17,7 @@ These are potentially inspirational resources for the editor's design. * [Unity game engine](https://unity.com/) * Scripts can expose values as text inputs, sliders, checkboxes, etc or even generate custom graphical inputs * Drag-n-drop game objects and component into script interfaces +* [How to Visualize Data Structures in VS Code](https://addyosmani.com/blog/visualize-data-structures-vscode/) ### Live Interactivity @@ -27,6 +28,11 @@ These are potentially inspirational resources for the editor's design. * [Xi](https://xi-editor.io/) modern text editor with concurrent editing (related to [Druid](https://github.com/linebender/druid)) * [Self](https://selflanguage.org/) programming language +### Debugging + +* [VS code debug visualization](https://marketplace.visualstudio.com/items?itemName=hediet.debug-visualizer) +* [Algorithm visualization for javascript](https://algorithm-visualizer.org) + ### Structured Editing * [Deuce](http://ravichugh.github.io/sketch-n-sketch/) (videos on the right) by [Ravi Chugh](http://people.cs.uchicago.edu/~rchugh/) and others diff --git a/roc-for-elm-programmers.md b/roc-for-elm-programmers.md index 52c1ef5226..64e184aefe 100644 --- a/roc-for-elm-programmers.md +++ b/roc-for-elm-programmers.md @@ -682,7 +682,7 @@ has the fields `x` and `y`. In Roc, you can do this like so: ```elm -table { height = 800, width = 600 } +table { height: 800, width: 600 } ``` ...and the `table` function will fill in its default values for `x` and `y`. diff --git a/shell.nix b/shell.nix index 0e7fd26188..176b8d07a5 100644 --- a/shell.nix +++ b/shell.nix @@ -11,9 +11,9 @@ let in { pkgs ? pinnedPkgs }: let - isOsX = builtins.currentSystem == "x86_64-darwin"; + isMacOS = builtins.currentSystem == "x86_64-darwin"; darwin-frameworks = - if isOsX then + if isMacOS then with pkgs.darwin.apple_sdk.frameworks; [ AppKit CoreFoundation @@ -26,6 +26,7 @@ let else [ ]; llvm = pkgs.llvm_10; + lld = pkgs.lld_10; # this should match llvm's version inputs = [ pkgs.rustup @@ -35,6 +36,8 @@ let pkgs.libffi pkgs.libxml2 pkgs.zlib + # faster builds - see https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#use-lld-for-the-linker + lld ]; in pkgs.mkShell { buildInputs = inputs ++ darwin-frameworks;