diff --git a/AUTHORS b/AUTHORS index 52c85f4493..ceb746617b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -43,3 +43,4 @@ Locria Cyber Matthias Beyer Tim Whiting Logan Lowder +Joshua Warner diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index df825d2935..35df489a83 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -82,7 +82,7 @@ There are also alternative installation options at http://releases.llvm.org/down ## Using Nix -:exclamation: **Our Nix setup is currently broken, you'll have to install manually for now** :exclamation: +:exclamation: **Our Nix setup is not yet working on MacOS, you'll have to install manually for now** :exclamation: ### Install @@ -94,7 +94,7 @@ First, install nix: `curl -L https://nixos.org/nix/install | sh` -If MacOS and using a version >= 10.15: +If you're on MacOS and using a OS version >= 10.15: `sh <(curl -L https://nixos.org/nix/install) --darwin-use-unencrypted-nix-store-volume` @@ -104,7 +104,7 @@ You may prefer to setup up the volume manually by following nix documentation. ### Usage -Now with nix installed you just need to run one command: +Now with nix installed, you just need to run one command: `nix-shell` @@ -120,33 +120,49 @@ You should be in a repl now. Have fun! ### Extra tips -If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.net/) and [lorri](https://github.com/target/lorri). Whenever you `cd` into `roc/`, they will automatically load the Nix dependecies into your current shell, so you never have to run nix-shell directly! +If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.net/) and [lorri](https://github.com/nix-community/lorri). Whenever you `cd` into `roc/`, they will automatically load the Nix dependecies into your current shell, so you never have to run nix-shell directly! ### Editor -When you want to run the editor from Ubuntu inside nix you need to install [nixGL](https://github.com/guibou/nixGL) as well: +`cargo run edit` should work from NixOS, if you use a nix-shell from inside another OS, follow the instructions below. + +#### Nvidia GPU + +Outside of a nix shell, execute the following: +``` +nix-channel --add https://github.com/guibou/nixGL/archive/main.tar.gz nixgl && nix-channel --update +nix-env -iA nixgl.auto.nixVulkanNvidia +``` +Running the editor does not work with `nix-shell --pure`. +``` +nix-shell +``` +460.91.03 may be different for you, type nixVulkanNvidia and press tab to autocomplete for your version. +``` +nixVulkanNvidia-460.91.03 cargo run edit +``` + +#### Integrated Intel Graphics + +:exclamation: ** Our Nix setup currently cannot run the editor with integrated intel graphics, see #1856 ** :exclamation: + +Outside of a nix shell, run: ```bash -nix-shell git clone https://github.com/guibou/nixGL cd nixGL -``` - -If you have an Nvidia graphics card, run: -``` -nix-env -f ./ -iA nixVulkanNvidia -``` -If you have integrated Intel graphics, run: -``` nix-env -f ./ -iA nixVulkanIntel ``` -Check the [nixGL repo](https://github.com/guibou/nixGL) for other configurations. -Now you should be able to run the editor: -```bash -cd roc -nixVulkanNvidia cargo run edit `# replace Nvidia with the config you chose in the previous step` +cd to the roc repo, and run (without --pure): ``` +nix-shell +nixVulkanIntel cargo run edit +``` + +#### Other configs + +Check the [nixGL repo](https://github.com/guibou/nixGL) for other graphics configurations. ## Troubleshooting @@ -197,6 +213,11 @@ on Windows. After lots of help from [**@IanMacKenzie**](https://github.com/IanMa Once all that was done, `cargo` ran successfully for Roc! +### Build speed on WSL/WSL2 + +If your Roc project folder is in the Windows filesystem but you're compiling from Linux, rebuilds may be as much as 20x slower than they should be! +Disk access during linking seems to be the bottleneck. It's recommended to move your folder to the Linux filesystem. + ## Use LLD for the linker Using [`lld` for Rust's linker](https://github.com/rust-lang/rust/issues/39915#issuecomment-538049306) diff --git a/Cargo.lock b/Cargo.lock index 94eef5d64c..8aaf014dd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -498,7 +498,6 @@ version = "0.1.0" dependencies = [ "bumpalo", "criterion", - "inlinable_string", "rlimit", "roc_cli", "roc_collections", @@ -1851,12 +1850,6 @@ dependencies = [ "syn 1.0.76", ] -[[package]] -name = "inlinable_string" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3094308123a0e9fd59659ce45e22de9f53fc1d2ac6e1feb9fef988e4f76cad77" - [[package]] name = "inplace_it" version = "0.3.3" @@ -3579,9 +3572,7 @@ dependencies = [ "im-rc 14.3.0", "indoc 0.3.6", "inkwell 0.1.0", - "inlinable_string", "libloading 0.6.7", - "maplit", "pretty_assertions 0.5.1", "quickcheck 0.8.5", "quickcheck_macros 0.8.0", @@ -3613,7 +3604,6 @@ name = "roc_builtins" version = "0.1.0" dependencies = [ "indoc 0.3.6", - "maplit", "pretty_assertions 0.5.1", "quickcheck 0.8.5", "quickcheck_macros 0.8.0", @@ -3631,7 +3621,6 @@ dependencies = [ "im 14.3.0", "im-rc 14.3.0", "indoc 0.3.6", - "maplit", "pretty_assertions 0.5.1", "quickcheck 0.8.5", "quickcheck_macros 0.8.0", @@ -3658,9 +3647,7 @@ dependencies = [ "im-rc 14.3.0", "indoc 0.3.6", "inkwell 0.1.0", - "libc", "libloading 0.6.7", - "maplit", "mimalloc", "pretty_assertions 0.5.1", "quickcheck 0.8.5", @@ -3724,7 +3711,6 @@ name = "roc_constrain" version = "0.1.0" dependencies = [ "indoc 0.3.6", - "maplit", "pretty_assertions 0.5.1", "quickcheck 0.8.5", "quickcheck_macros 0.8.0", @@ -3742,7 +3728,6 @@ name = "roc_docs" version = "0.1.0" dependencies = [ "bumpalo", - "maplit", "pretty_assertions 0.5.1", "pulldown-cmark", "roc_ast", @@ -3750,7 +3735,6 @@ dependencies = [ "roc_can", "roc_code_markup", "roc_collections", - "roc_fmt", "roc_load", "roc_module", "roc_parse", @@ -3781,7 +3765,6 @@ dependencies = [ "indoc 1.0.3", "libc", "log", - "maplit", "nonempty", "page_size", "palette", @@ -3796,7 +3779,6 @@ dependencies = [ "roc_can", "roc_code_markup", "roc_collections", - "roc_fmt", "roc_load", "roc_module", "roc_parse", @@ -3807,7 +3789,6 @@ dependencies = [ "roc_types", "roc_unify", "rodio", - "ropey", "serde", "snafu", "tempfile", @@ -3817,7 +3798,6 @@ dependencies = [ "wgpu", "wgpu_glyph", "winit", - "zerocopy", ] [[package]] @@ -3828,7 +3808,6 @@ dependencies = [ "im 14.3.0", "im-rc 14.3.0", "indoc 0.3.6", - "maplit", "pretty_assertions 0.5.1", "quickcheck 0.8.5", "quickcheck_macros 0.8.0", @@ -3847,9 +3826,7 @@ dependencies = [ "im-rc 14.3.0", "indoc 0.3.6", "itertools 0.9.0", - "libc", "libloading 0.6.7", - "maplit", "object 0.24.0", "pretty_assertions 0.5.1", "quickcheck 0.8.5", @@ -3884,8 +3861,6 @@ dependencies = [ "im-rc 14.3.0", "indoc 0.3.6", "inkwell 0.1.0", - "libc", - "maplit", "morphic_lib", "pretty_assertions 0.5.1", "quickcheck 0.8.5", @@ -3915,7 +3890,6 @@ version = "0.1.0" dependencies = [ "bumpalo", "indoc 0.3.6", - "libc", "parity-wasm", "pretty_assertions 0.5.1", "roc_builtins", @@ -3992,7 +3966,6 @@ dependencies = [ "bumpalo", "indoc 0.3.6", "lazy_static", - "maplit", "pretty_assertions 0.5.1", "roc_collections", "roc_ident", @@ -4009,7 +3982,6 @@ dependencies = [ "hashbrown 0.11.2", "indoc 0.3.6", "linked-hash-map", - "maplit", "morphic_lib", "pretty_assertions 0.5.1", "quickcheck 0.8.5", @@ -4051,7 +4023,6 @@ name = "roc_problem" version = "0.1.0" dependencies = [ "indoc 0.3.6", - "maplit", "pretty_assertions 0.5.1", "quickcheck 0.8.5", "quickcheck_macros 0.8.0", @@ -4074,7 +4045,6 @@ dependencies = [ "im 14.3.0", "im-rc 14.3.0", "indoc 0.3.6", - "maplit", "pretty_assertions 0.5.1", "quickcheck 0.8.5", "quickcheck_macros 0.8.0", @@ -4098,7 +4068,6 @@ version = "0.1.0" dependencies = [ "bumpalo", "indoc 0.3.6", - "maplit", "pretty_assertions 0.5.1", "quickcheck 0.8.5", "quickcheck_macros 0.8.0", @@ -4125,7 +4094,6 @@ name = "roc_types" version = "0.1.0" dependencies = [ "indoc 0.3.6", - "maplit", "pretty_assertions 0.5.1", "quickcheck 0.8.5", "quickcheck_macros 0.8.0", @@ -4141,7 +4109,6 @@ name = "roc_unify" version = "0.1.0" dependencies = [ "indoc 0.3.6", - "maplit", "pretty_assertions 0.5.1", "quickcheck 0.8.5", "quickcheck_macros 0.8.0", @@ -4170,15 +4137,6 @@ dependencies = [ "minimp3", ] -[[package]] -name = "ropey" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150aff6deb25b20ed110889f070a678bcd1033e46e5e9d6fb1abeab17947f28" -dependencies = [ - "smallvec", -] - [[package]] name = "rustc-demangle" version = "0.1.21" @@ -4624,18 +4582,6 @@ dependencies = [ "unicode-xid 0.2.2", ] -[[package]] -name = "synstructure" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "474aaa926faa1603c40b7885a9eaea29b444d1cb2850cb7c0e37bb1a4182f4fa" -dependencies = [ - "proc-macro2 1.0.29", - "quote 1.0.9", - "syn 1.0.76", - "unicode-xid 0.2.2", -] - [[package]] name = "target-lexicon" version = "0.12.2" @@ -4677,7 +4623,6 @@ dependencies = [ "inkwell 0.1.0", "libc", "libloading 0.6.7", - "maplit", "quickcheck 0.8.5", "quickcheck_macros 0.8.0", "roc_build", @@ -4710,31 +4655,16 @@ name = "test_mono" version = "0.1.0" dependencies = [ "bumpalo", - "either", - "im 14.3.0", - "im-rc 14.3.0", "indoc 0.3.6", - "libc", - "libloading 0.6.7", "pretty_assertions 0.5.1", "quickcheck 0.8.5", "quickcheck_macros 0.8.0", - "roc_build", "roc_builtins", "roc_can", "roc_collections", - "roc_constrain", "roc_load", "roc_module", "roc_mono", - "roc_parse", - "roc_problem", - "roc_region", - "roc_reporting", - "roc_solve", - "roc_types", - "roc_unify", - "target-lexicon", "test_mono_macros", ] @@ -4742,12 +4672,34 @@ dependencies = [ name = "test_mono_macros" version = "0.1.0" dependencies = [ - "darling 0.10.2", "proc-macro2 1.0.29", "quote 1.0.9", "syn 1.0.76", ] +[[package]] +name = "test_wasm" +version = "0.1.0" +dependencies = [ + "bumpalo", + "indoc 0.3.6", + "libc", + "parity-wasm", + "pretty_assertions 0.5.1", + "roc_builtins", + "roc_can", + "roc_collections", + "roc_gen_wasm", + "roc_load", + "roc_module", + "roc_std", + "roc_types", + "target-lexicon", + "tempfile", + "wasmer", + "wasmer-wasi", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -5749,24 +5701,3 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ "linked-hash-map", ] - -[[package]] -name = "zerocopy" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6580539ad917b7c026220c4b3f2c08d52ce54d6ce0dc491e66002e35388fab46" -dependencies = [ - "byteorder", - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d498dbd1fd7beb83c86709ae1c33ca50942889473473d287d56ce4770a18edfb" -dependencies = [ - "proc-macro2 1.0.29", - "syn 1.0.76", - "synstructure", -] diff --git a/Cargo.toml b/Cargo.toml index a2fe43c612..2efdb19d15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ members = [ "compiler/build", "compiler/arena_pool", "compiler/test_gen", + "compiler/test_wasm", "vendor/ena", "vendor/inkwell", "vendor/pathfinding", diff --git a/Earthfile b/Earthfile index c76928e855..9ef2fb0183 100644 --- a/Earthfile +++ b/Earthfile @@ -1,4 +1,4 @@ -FROM rust:1.54-slim-bullseye +FROM rust:1.56.1-slim-bullseye WORKDIR /earthbuild prep-debian: diff --git a/ast/Cargo.toml b/ast/Cargo.toml index a379da9d8e..7abd27e43f 100644 --- a/ast/Cargo.toml +++ b/ast/Cargo.toml @@ -23,7 +23,7 @@ libc = "0.2" page_size = "0.4" snafu = { version = "0.6", features = ["backtraces"] } ven_graph = { path = "../vendor/pathfinding" } -indoc = "1.0" [dev-dependencies] pretty_assertions = "0.6" +indoc = "1.0" diff --git a/ast/src/mem_pool/pool.rs b/ast/src/mem_pool/pool.rs index ab4ae5548d..09baf3701c 100644 --- a/ast/src/mem_pool/pool.rs +++ b/ast/src/mem_pool/pool.rs @@ -10,8 +10,9 @@ /// /// Pages also use the node value 0 (all 0 bits) to mark nodes as unoccupied. /// This is important for performance. -use libc::{c_void, MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE}; +use libc::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE}; use std::any::type_name; +use std::ffi::c_void; use std::marker::PhantomData; use std::mem::size_of; use std::ptr::null; diff --git a/ast/src/mem_pool/pool_str.rs b/ast/src/mem_pool/pool_str.rs index 435d4586bb..d4ea9c28bf 100644 --- a/ast/src/mem_pool/pool_str.rs +++ b/ast/src/mem_pool/pool_str.rs @@ -1,6 +1,6 @@ use super::pool::{NodeId, Pool, NODE_BYTES}; use super::shallow_clone::ShallowClone; -use libc::c_void; +use std::ffi::c_void; use std::marker::PhantomData; use std::mem::size_of; diff --git a/ast/src/mem_pool/pool_vec.rs b/ast/src/mem_pool/pool_vec.rs index 65c9e89b1b..75b3733fdf 100644 --- a/ast/src/mem_pool/pool_vec.rs +++ b/ast/src/mem_pool/pool_vec.rs @@ -1,8 +1,8 @@ use super::pool::{NodeId, Pool, NODE_BYTES}; use super::shallow_clone::ShallowClone; -use libc::c_void; use std::any::type_name; use std::cmp::Ordering; +use std::ffi::c_void; use std::marker::PhantomData; use std::mem::size_of; diff --git a/cli/Cargo.toml b/cli/Cargo.toml index ffae9d11f7..7f46d6117c 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -65,7 +65,6 @@ rustyline-derive = { git = "https://github.com/rtfeldman/rustyline", tag = "prom im = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version! bumpalo = { version = "3.2", features = ["collections"] } -libc = "0.2" libloading = "0.6" mimalloc = { version = "0.1.26", default-features = false } @@ -78,7 +77,6 @@ wasmer-wasi = "2.0.0" [dev-dependencies] pretty_assertions = "0.5.1" -maplit = "1.0.1" indoc = "0.3.3" quickcheck = "0.8" quickcheck_macros = "0.8" @@ -90,4 +88,3 @@ cli_utils = { path = "cli_utils" } [[bench]] name = "time_bench" harness = false - diff --git a/cli/cli_utils/Cargo.toml b/cli/cli_utils/Cargo.toml index faf1da7fa9..6a5ceeccc7 100644 --- a/cli/cli_utils/Cargo.toml +++ b/cli/cli_utils/Cargo.toml @@ -15,8 +15,7 @@ roc_collections = { path = "../../compiler/collections" } roc_load = { path = "../../compiler/load" } roc_module = { path = "../../compiler/module" } bumpalo = { version = "3.6.1", features = ["collections"] } -criterion = { git = "https://github.com/Anton-4/criterion.rs"} -inlinable_string = "0.1" +criterion = { git = "https://github.com/Anton-4/criterion.rs"} serde = { version = "1.0", features = ["derive"] } serde-xml-rs = "0.4" strip-ansi-escapes = "0.1" diff --git a/cli/src/lib.rs b/cli/src/lib.rs index ea9de56937..44da4b3772 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -119,7 +119,7 @@ pub fn build_app<'a>() -> App<'a> { ) .subcommand( App::new(CMD_DOCS) - .about("Generate documentation for Roc modules") + .about("Generate documentation for Roc modules (Work In Progress)") .arg(Arg::with_name(DIRECTORY_OR_FILES) .index(1) .multiple(true) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index e62066ab69..71afcc29d0 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -362,7 +362,7 @@ mod cli_run { stdin: &[], input_file: Some("examples/hello.false"), expected_ending:"Hello, World!\n", - use_valgrind: false, + use_valgrind: true, } }, } diff --git a/compiler/build/Cargo.toml b/compiler/build/Cargo.toml index 5c525ea89c..65ca09e8a1 100644 --- a/compiler/build/Cargo.toml +++ b/compiler/build/Cargo.toml @@ -27,7 +27,6 @@ roc_std = { path = "../../roc_std" } im = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version! bumpalo = { version = "3.6.1", features = ["collections"] } -inlinable_string = "0.1.0" libloading = "0.6" tempfile = "3.1.0" serde_json = "1.0" @@ -36,7 +35,6 @@ target-lexicon = "0.12.2" [dev-dependencies] pretty_assertions = "0.5.1" -maplit = "1.0.1" indoc = "0.3.3" quickcheck = "0.8" quickcheck_macros = "0.8" diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index fa3966eac6..8d2f7c36f1 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -1,4 +1,4 @@ -use crate::target::arch_str; +use crate::target::{arch_str, target_triple_str}; #[cfg(feature = "llvm")] use libloading::{Error, Library}; use roc_builtins::bitcode; @@ -372,6 +372,20 @@ pub fn rebuild_host( shared_lib_path, ) } + + Architecture::Aarch64(_) => { + let emit_bin = format!("-femit-bin={}", host_dest_native.to_str().unwrap()); + build_zig_host_native( + &env_path, + &env_home, + &emit_bin, + zig_host_src.to_str().unwrap(), + zig_str_path.to_str().unwrap(), + target_triple_str(target), + opt_level, + shared_lib_path, + ) + } _ => panic!("Unsupported architecture {:?}", target.architecture), }; diff --git a/compiler/builtins/Cargo.toml b/compiler/builtins/Cargo.toml index b7009bc09e..7774962648 100644 --- a/compiler/builtins/Cargo.toml +++ b/compiler/builtins/Cargo.toml @@ -12,7 +12,6 @@ roc_module = { path = "../module" } roc_types = { path = "../types" } [dev-dependencies] pretty_assertions = "0.5.1" -maplit = "1.0.1" indoc = "0.3.3" quickcheck = "0.8" quickcheck_macros = "0.8" diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index a2f9cfd6df..4ceef6582f 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -140,6 +140,7 @@ const Caller0 = fn (?[*]u8, ?[*]u8) callconv(.C) void; const Caller1 = fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void; const Caller2 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void; const Caller3 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void; +const Caller4 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void; pub fn listReverse(list: RocList, alignment: u32, element_width: usize, update_mode: UpdateMode) callconv(.C) RocList { if (list.bytes) |source_ptr| { @@ -352,6 +353,70 @@ pub fn listMap3( } } +pub fn listMap4( + list1: RocList, + list2: RocList, + list3: RocList, + list4: RocList, + caller: Caller4, + data: Opaque, + inc_n_data: IncN, + data_is_owned: bool, + alignment: u32, + a_width: usize, + b_width: usize, + c_width: usize, + d_width: usize, + e_width: usize, + dec_a: Dec, + dec_b: Dec, + dec_c: Dec, + dec_d: Dec, +) callconv(.C) RocList { + const output_length = std.math.min(std.math.min(list1.len(), list2.len()), std.math.min(list3.len(), list4.len())); + + decrementTail(list1, output_length, a_width, dec_a); + decrementTail(list2, output_length, b_width, dec_b); + decrementTail(list3, output_length, c_width, dec_c); + decrementTail(list4, output_length, d_width, dec_d); + + if (data_is_owned) { + inc_n_data(data, output_length); + } + + if (list1.bytes) |source_a| { + if (list2.bytes) |source_b| { + if (list3.bytes) |source_c| { + if (list4.bytes) |source_d| { + const output = RocList.allocate(alignment, output_length, e_width); + const target_ptr = output.bytes orelse unreachable; + + var i: usize = 0; + while (i < output_length) : (i += 1) { + const element_a = source_a + i * a_width; + const element_b = source_b + i * b_width; + const element_c = source_c + i * c_width; + const element_d = source_d + i * d_width; + const target = target_ptr + i * e_width; + + caller(data, element_a, element_b, element_c, element_d, target); + } + + return output; + } else { + return RocList.empty(); + } + } else { + return RocList.empty(); + } + } else { + return RocList.empty(); + } + } else { + return RocList.empty(); + } +} + pub fn listKeepIf( list: RocList, caller: Caller1, diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index a356964e25..aeccad39ca 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -26,6 +26,7 @@ comptime { exportListFn(list.listMap, "map"); exportListFn(list.listMap2, "map2"); exportListFn(list.listMap3, "map3"); + exportListFn(list.listMap4, "map4"); exportListFn(list.listMapWithIndex, "map_with_index"); exportListFn(list.listKeepIf, "keep_if"); exportListFn(list.listWalk, "walk"); diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index ae565b83f4..d5f1148292 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -25,6 +25,7 @@ interface List map, map2, map3, + map4, mapWithIndex, mapOrDrop, mapJoin, @@ -269,6 +270,11 @@ map2 : List a, List b, (a, b -> c) -> List c ## Repeat until a list runs out of elements. map3 : List a, List b, List c, (a, b, c -> d) -> List d +## Run a transformation function on the first element of each list, +## and use that as the first element in the returned list. +## Repeat until a list runs out of elements. +map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e + ## This works like [List.map], except it also passes the index ## of the element to the conversion function. mapWithIndex : List before, (before, Nat -> after) -> List after diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index c517094b8a..1876c8100c 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -165,6 +165,7 @@ pub const SET_FROM_LIST: &str = "roc_builtins.dict.set_from_list"; pub const LIST_MAP: &str = "roc_builtins.list.map"; pub const LIST_MAP2: &str = "roc_builtins.list.map2"; pub const LIST_MAP3: &str = "roc_builtins.list.map3"; +pub const LIST_MAP4: &str = "roc_builtins.list.map4"; pub const LIST_MAP_WITH_INDEX: &str = "roc_builtins.list.map_with_index"; pub const LIST_KEEP_IF: &str = "roc_builtins.list.keep_if"; pub const LIST_KEEP_OKS: &str = "roc_builtins.list.keep_oks"; diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 8ddc7e9990..1ced33cc9b 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -64,7 +64,7 @@ const TVAR1: VarId = VarId::from_u32(1); const TVAR2: VarId = VarId::from_u32(2); const TVAR3: VarId = VarId::from_u32(3); const TVAR4: VarId = VarId::from_u32(4); -const TOP_LEVEL_CLOSURE_VAR: VarId = VarId::from_u32(5); +const TOP_LEVEL_CLOSURE_VAR: VarId = VarId::from_u32(10); pub fn types() -> MutMap { let mut types = HashMap::with_capacity_and_hasher(NUM_BUILTIN_IMPORTS, default_hasher()); @@ -930,6 +930,27 @@ pub fn types() -> MutMap { ) }; + { + let_tvars! {a, b, c, d, e, cvar}; + + // map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e + add_top_level_function_type!( + Symbol::LIST_MAP4, + vec![ + list_type(flex(a)), + list_type(flex(b)), + list_type(flex(c)), + list_type(flex(d)), + closure( + vec![flex(a), flex(b), flex(c), flex(d)], + cvar, + Box::new(flex(e)) + ), + ], + Box::new(list_type(flex(e))), + ) + }; + // append : List elem, elem -> List elem add_top_level_function_type!( Symbol::LIST_APPEND, diff --git a/compiler/can/Cargo.toml b/compiler/can/Cargo.toml index e5f5764a9f..2d2fd8aa62 100644 --- a/compiler/can/Cargo.toml +++ b/compiler/can/Cargo.toml @@ -20,7 +20,6 @@ bumpalo = { version = "3.6.1", features = ["collections"] } [dev-dependencies] pretty_assertions = "0.5.1" -maplit = "1.0.1" indoc = "0.3.3" quickcheck = "0.8" quickcheck_macros = "0.8" diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 711bb51fef..fb55cf56fa 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -89,6 +89,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option LIST_MAP => list_map, LIST_MAP2 => list_map2, LIST_MAP3 => list_map3, + LIST_MAP4 => list_map4, LIST_DROP => list_drop, LIST_DROP_AT => list_drop_at, LIST_DROP_LAST => list_drop_last, @@ -290,6 +291,41 @@ fn lowlevel_4(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def { ) } +fn lowlevel_5(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def { + let arg1_var = var_store.fresh(); + let arg2_var = var_store.fresh(); + let arg3_var = var_store.fresh(); + let arg4_var = var_store.fresh(); + let arg5_var = var_store.fresh(); + let ret_var = var_store.fresh(); + + let body = RunLowLevel { + op, + args: vec![ + (arg1_var, Var(Symbol::ARG_1)), + (arg2_var, Var(Symbol::ARG_2)), + (arg3_var, Var(Symbol::ARG_3)), + (arg4_var, Var(Symbol::ARG_4)), + (arg5_var, Var(Symbol::ARG_5)), + ], + ret_var, + }; + + defn( + symbol, + vec![ + (arg1_var, Symbol::ARG_1), + (arg2_var, Symbol::ARG_2), + (arg3_var, Symbol::ARG_3), + (arg4_var, Symbol::ARG_4), + (arg5_var, Symbol::ARG_5), + ], + var_store, + body, + ret_var, + ) +} + /// Num.maxInt : Int fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def { let int_var = var_store.fresh(); @@ -2541,11 +2577,16 @@ fn list_map2(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_3(symbol, LowLevel::ListMap2, var_store) } -/// List.map3 : List a, List b, (a, b -> c) -> List c +/// List.map3 : List a, List b, List c, (a, b, c -> d) -> List d fn list_map3(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_4(symbol, LowLevel::ListMap3, var_store) } +/// List.map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e +fn list_map4(symbol: Symbol, var_store: &mut VarStore) -> Def { + lowlevel_5(symbol, LowLevel::ListMap4, var_store) +} + /// List.sortWith : List a, (a, a -> Ordering) -> List a fn list_sort_with(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_2(symbol, LowLevel::ListSortWith, var_store) diff --git a/compiler/constrain/Cargo.toml b/compiler/constrain/Cargo.toml index c5c21def52..39b953e7cf 100644 --- a/compiler/constrain/Cargo.toml +++ b/compiler/constrain/Cargo.toml @@ -16,7 +16,6 @@ roc_builtins = { path = "../builtins" } [dev-dependencies] pretty_assertions = "0.5.1" -maplit = "1.0.1" indoc = "0.3.3" quickcheck = "0.8" quickcheck_macros = "0.8" diff --git a/compiler/fmt/Cargo.toml b/compiler/fmt/Cargo.toml index 5fbeb55b4d..d800b7fd1a 100644 --- a/compiler/fmt/Cargo.toml +++ b/compiler/fmt/Cargo.toml @@ -16,7 +16,6 @@ bumpalo = { version = "3.6.1", features = ["collections"] } [dev-dependencies] pretty_assertions = "0.5.1" -maplit = "1.0.1" indoc = "0.3.3" quickcheck = "0.8" quickcheck_macros = "0.8" diff --git a/compiler/gen_dev/Cargo.toml b/compiler/gen_dev/Cargo.toml index 07b85fd205..4cf58360d1 100644 --- a/compiler/gen_dev/Cargo.toml +++ b/compiler/gen_dev/Cargo.toml @@ -9,12 +9,10 @@ edition = "2018" [dependencies] roc_collections = { path = "../collections" } roc_region = { path = "../region" } -roc_load = { path = "../load" } roc_module = { path = "../module" } roc_problem = { path = "../problem" } roc_types = { path = "../types" } roc_builtins = { path = "../builtins" } -roc_constrain = { path = "../constrain" } roc_unify = { path = "../unify" } roc_solve = { path = "../solve" } roc_mono = { path = "../mono" } @@ -22,7 +20,6 @@ im = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version! bumpalo = { version = "3.6.1", features = ["collections"] } target-lexicon = "0.12.2" -libloading = "0.6" object = { version = "0.24", features = ["write"] } [dev-dependencies] @@ -30,17 +27,18 @@ roc_can = { path = "../can" } roc_parse = { path = "../parse" } roc_reporting = { path = "../reporting" } roc_build = { path = "../build" } +roc_load = { path = "../load" } +roc_constrain = { path = "../constrain" } roc_std = { path = "../../roc_std" } pretty_assertions = "0.5.1" -maplit = "1.0.1" indoc = "0.3.3" quickcheck = "0.8" quickcheck_macros = "0.8" tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] } bumpalo = { version = "3.6.1", features = ["collections"] } -libc = "0.2" tempfile = "3.1.0" itertools = "0.9" +libloading = "0.6" [features] target-aarch64 = ["roc_build/target-aarch64"] diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 008633c202..8df364f6a8 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -830,8 +830,9 @@ impl< layout: &Layout<'a>, fields: &'a [Symbol], ) -> Result<(), String> { + let struct_size = layout.stack_size(PTR_SIZE); + if let Layout::Struct(field_layouts) = layout { - let struct_size = layout.stack_size(PTR_SIZE); if struct_size > 0 { let offset = self.claim_stack_size(struct_size)?; self.symbol_storage_map.insert( @@ -862,7 +863,6 @@ impl< Ok(()) } else { // This is a single element struct. Just copy the single field to the stack. - let struct_size = layout.stack_size(PTR_SIZE); let offset = self.claim_stack_size(struct_size)?; self.symbol_storage_map.insert( *sym, diff --git a/compiler/gen_llvm/Cargo.toml b/compiler/gen_llvm/Cargo.toml index eaec8982bd..56484a3262 100644 --- a/compiler/gen_llvm/Cargo.toml +++ b/compiler/gen_llvm/Cargo.toml @@ -31,10 +31,8 @@ roc_load = { path = "../load" } roc_reporting = { path = "../reporting" } roc_build = { path = "../build" } pretty_assertions = "0.5.1" -maplit = "1.0.1" indoc = "0.3.3" quickcheck = "0.8" quickcheck_macros = "0.8" tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] } bumpalo = { version = "3.6.1", features = ["collections"] } -libc = "0.2" diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 55d35a481d..5483f8a2d8 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -10,9 +10,9 @@ use crate::llvm::build_hash::generic_hash; use crate::llvm::build_list::{ self, allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains, list_drop, list_drop_at, list_get_unsafe, list_join, list_keep_errs, - list_keep_if, list_keep_oks, list_len, list_map, list_map2, list_map3, list_map_with_index, - list_prepend, list_range, list_repeat, list_reverse, list_set, list_single, list_sort_with, - list_swap, + list_keep_if, list_keep_oks, list_len, list_map, list_map2, list_map3, list_map4, + list_map_with_index, list_prepend, list_range, list_repeat, list_reverse, list_set, + list_single, list_sort_with, list_swap, }; use crate::llvm::build_str::{ empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, @@ -1453,7 +1453,33 @@ pub fn build_tag<'a, 'ctx, 'env>( UnionLayout::NonRecursive(tags) => { debug_assert!(union_size > 1); - let ctx = env.context; + let internal_type = block_of_memory_slices(env.context, tags, env.ptr_bytes); + + let tag_id_type = basic_type_from_layout(env, &tag_id_layout).into_int_type(); + let wrapper_type = env + .context + .struct_type(&[internal_type, tag_id_type.into()], false); + let result_alloca = env.builder.build_alloca(wrapper_type, "tag_opaque"); + + // Initialize all memory of the alloca. This _should_ not be required, but currently + // LLVM can access uninitialized memory after applying some optimizations. Hopefully + // we can in the future adjust code gen so this is no longer an issue. + // + // An example is + // + // main : Task.Task {} [] + // main = + // when List.len [ Ok "foo", Err 42, Ok "spam" ] is + // n -> Task.putLine (Str.fromInt n) + // + // Here the decrement function of result must first check it's an Ok tag, + // then defers to string decrement, which must check is the string is small or large + // + // After inlining, those checks are combined. That means that even if the tag is Err, + // a check is done on the "string" to see if it is big or small, which will touch the + // uninitialized memory. + let all_zeros = wrapper_type.const_zero(); + env.builder.build_store(result_alloca, all_zeros); // Determine types let num_fields = arguments.len() + 1; @@ -1484,54 +1510,46 @@ pub fn build_tag<'a, 'ctx, 'env>( } } } - - // Create the struct_type - let struct_type = ctx.struct_type(field_types.into_bump_slice(), false); - - // Insert field exprs into struct_val - let struct_val = - struct_from_fields(env, struct_type, field_vals.into_iter().enumerate()); - - // How we create tag values - // - // The memory layout of tags can be different. e.g. in - // - // [ Ok Int, Err Str ] - // - // the `Ok` tag stores a 64-bit integer, the `Err` tag stores a struct. - // All tags of a union must have the same length, for easy addressing (e.g. array lookups). - // So we need to ask for the maximum of all tag's sizes, even if most tags won't use - // all that memory, and certainly won't use it in the same way (the tags have fields of - // different types/sizes) - // - // In llvm, we must be explicit about the type of value we're creating: we can't just - // make a unspecified block of memory. So what we do is create a byte array of the - // desired size. Then when we know which tag we have (which is here, in this function), - // we need to cast that down to the array of bytes that llvm expects - // - // There is the bitcast instruction, but it doesn't work for arrays. So we need to jump - // through some hoops using store and load to get this to work: the array is put into a - // one-element struct, which can be cast to the desired type. - // - // This tricks comes from - // https://github.com/raviqqe/ssf/blob/bc32aae68940d5bddf5984128e85af75ca4f4686/ssf-llvm/src/expression_compiler.rs#L116 - - let internal_type = block_of_memory_slices(env.context, tags, env.ptr_bytes); - - let data = cast_tag_to_block_of_memory(env, struct_val, internal_type); - let tag_id_type = basic_type_from_layout(env, &tag_id_layout).into_int_type(); - let wrapper_type = env - .context - .struct_type(&[data.get_type(), tag_id_type.into()], false); + // store the tag id + let tag_id_ptr = env + .builder + .build_struct_gep(result_alloca, TAG_ID_INDEX, "get_opaque_data") + .unwrap(); let tag_id_intval = tag_id_type.const_int(tag_id as u64, false); + env.builder.build_store(tag_id_ptr, tag_id_intval); - let field_vals = [ - (TAG_DATA_INDEX as usize, data), - (TAG_ID_INDEX as usize, tag_id_intval.into()), - ]; + // Create the struct_type + let struct_type = env + .context + .struct_type(field_types.into_bump_slice(), false); - struct_from_fields(env, wrapper_type, field_vals.iter().copied()).into() + let struct_opaque_ptr = env + .builder + .build_struct_gep(result_alloca, TAG_DATA_INDEX, "get_opaque_data") + .unwrap(); + let struct_ptr = env.builder.build_pointer_cast( + struct_opaque_ptr, + struct_type.ptr_type(AddressSpace::Generic), + "to_specific", + ); + + // Insert field exprs into struct_val + //let struct_val = + //struct_from_fields(env, struct_type, field_vals.into_iter().enumerate()); + + // Insert field exprs into struct_val + for (index, field_val) in field_vals.iter().copied().enumerate() { + let index: u32 = index as u32; + + let ptr = env + .builder + .build_struct_gep(struct_ptr, index, "get_tag_field_ptr") + .unwrap(); + env.builder.build_store(ptr, field_val); + } + + env.builder.build_load(result_alloca, "load_result") } UnionLayout::Recursive(tags) => { debug_assert!(union_size > 1); @@ -2653,14 +2671,6 @@ pub fn complex_bitcast_struct_struct<'ctx>( complex_bitcast(builder, from_value.into(), to_type.into(), name).into_struct_value() } -fn cast_tag_to_block_of_memory<'a, 'ctx, 'env>( - env: &Env<'a, 'ctx, 'env>, - from_value: StructValue<'ctx>, - to_type: BasicTypeEnum<'ctx>, -) -> BasicValueEnum<'ctx> { - complex_bitcast_check_size(env, from_value.into(), to_type, "tag_to_block_of_memory") -} - pub fn cast_block_of_memory_to_tag<'ctx>( builder: &Builder<'ctx>, from_value: StructValue<'ctx>, @@ -4609,6 +4619,67 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( _ => unreachable!("invalid list layout"), } } + ListMap4 { xs, ys, zs, ws } => { + let (list1, list1_layout) = load_symbol_and_layout(scope, &xs); + let (list2, list2_layout) = load_symbol_and_layout(scope, &ys); + let (list3, list3_layout) = load_symbol_and_layout(scope, &zs); + let (list4, list4_layout) = load_symbol_and_layout(scope, &ws); + + let (function, closure, closure_layout) = function_details!(); + + match ( + list1_layout, + list2_layout, + list3_layout, + list4_layout, + return_layout, + ) { + ( + Layout::Builtin(Builtin::List(element1_layout)), + Layout::Builtin(Builtin::List(element2_layout)), + Layout::Builtin(Builtin::List(element3_layout)), + Layout::Builtin(Builtin::List(element4_layout)), + Layout::Builtin(Builtin::List(result_layout)), + ) => { + let argument_layouts = &[ + **element1_layout, + **element2_layout, + **element3_layout, + **element4_layout, + ]; + + let roc_function_call = roc_function_call( + env, + layout_ids, + function, + closure, + closure_layout, + function_owns_closure_data, + argument_layouts, + ); + + list_map4( + env, + layout_ids, + roc_function_call, + list1, + list2, + list3, + list4, + element1_layout, + element2_layout, + element3_layout, + element4_layout, + result_layout, + ) + } + (Layout::Builtin(Builtin::EmptyList), _, _, _, _) + | (_, Layout::Builtin(Builtin::EmptyList), _, _, _) + | (_, _, Layout::Builtin(Builtin::EmptyList), _, _) + | (_, _, _, Layout::Builtin(Builtin::EmptyList), _) => empty_list(env), + _ => unreachable!("invalid list layout"), + } + } ListMapWithIndex { xs } => { // List.mapWithIndex : List before, (Nat, before -> after) -> List after let (list, list_layout) = load_symbol_and_layout(scope, &xs); @@ -5640,7 +5711,7 @@ fn run_low_level<'a, 'ctx, 'env>( cond } - ListMap | ListMap2 | ListMap3 | ListMapWithIndex | ListKeepIf | ListWalk + ListMap | ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | DictWalk => unreachable!("these are higher order, and are handled elsewhere"), } diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index 3d80912689..8b84bb6677 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -821,6 +821,51 @@ pub fn list_map3<'a, 'ctx, 'env>( ) } +pub fn list_map4<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + roc_function_call: RocFunctionCall<'ctx>, + list1: BasicValueEnum<'ctx>, + list2: BasicValueEnum<'ctx>, + list3: BasicValueEnum<'ctx>, + list4: BasicValueEnum<'ctx>, + element1_layout: &Layout<'a>, + element2_layout: &Layout<'a>, + element3_layout: &Layout<'a>, + element4_layout: &Layout<'a>, + result_layout: &Layout<'a>, +) -> BasicValueEnum<'ctx> { + let dec_a = build_dec_wrapper(env, layout_ids, element1_layout); + let dec_b = build_dec_wrapper(env, layout_ids, element2_layout); + let dec_c = build_dec_wrapper(env, layout_ids, element3_layout); + let dec_d = build_dec_wrapper(env, layout_ids, element4_layout); + + call_bitcode_fn_returns_list( + env, + &[ + pass_list_cc(env, list1), + pass_list_cc(env, list2), + pass_list_cc(env, list3), + pass_list_cc(env, list4), + roc_function_call.caller.into(), + pass_as_opaque(env, roc_function_call.data), + roc_function_call.inc_n_data.into(), + roc_function_call.data_is_owned.into(), + env.alignment_intvalue(result_layout), + layout_width(env, element1_layout), + layout_width(env, element2_layout), + layout_width(env, element3_layout), + layout_width(env, element4_layout), + layout_width(env, result_layout), + dec_a.as_global_value().as_pointer_value().into(), + dec_b.as_global_value().as_pointer_value().into(), + dec_c.as_global_value().as_pointer_value().into(), + dec_d.as_global_value().as_pointer_value().into(), + ], + bitcode::LIST_MAP4, + ) +} + /// List.concat : List elem, List elem -> List elem pub fn list_concat<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, @@ -965,6 +1010,8 @@ where let ctx = env.context; let builder = env.builder; + let entry = env.builder.get_insert_block().unwrap(); + // constant 1i64 let one = env.ptr_int().const_int(1, false); @@ -976,15 +1023,15 @@ where builder.build_unconditional_branch(loop_bb); builder.position_at_end(loop_bb); - let curr_index = builder - .build_load(index_alloca, index_name) - .into_int_value(); - let next_index = builder.build_int_add(curr_index, one, "nextindex"); + let current_index_phi = env.builder.build_phi(env.ptr_int(), "current_index"); + let current_index = current_index_phi.as_basic_value().into_int_value(); - builder.build_store(index_alloca, next_index); + let next_index = builder.build_int_add(current_index, one, "next_index"); + + current_index_phi.add_incoming(&[(&next_index, loop_bb), (&env.ptr_int().const_zero(), entry)]); // The body of the loop - loop_fn(curr_index); + loop_fn(current_index); // #index < end let loop_end_cond = bounds_check_comparison(builder, next_index, end); diff --git a/compiler/gen_wasm/Cargo.toml b/compiler/gen_wasm/Cargo.toml index 93b4414d54..9fb7561121 100644 --- a/compiler/gen_wasm/Cargo.toml +++ b/compiler/gen_wasm/Cargo.toml @@ -15,7 +15,6 @@ parity-wasm = { git = "https://github.com/brian-carroll/parity-wasm", branch = " roc_std = { path = "../../roc_std" } wasmer = "2.0.0" -wasmer-wasi = "2.0.0" [dev-dependencies] roc_can = { path = "../can" } @@ -25,6 +24,6 @@ roc_types = { path = "../types" } roc_module = { path = "../module" } indoc = "0.3.3" pretty_assertions = "0.5.1" -libc = "0.2" target-lexicon = "0.12.2" tempfile = "3.1.0" +wasmer-wasi = "2.0.0" diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 44d3adf41d..963d0a5585 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -1,6 +1,6 @@ use bumpalo::collections::Vec; use parity_wasm::builder; -use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder}; +use parity_wasm::builder::{FunctionDefinition, ModuleBuilder}; use roc_collections::all::MutMap; use roc_module::low_level::LowLevel; @@ -10,8 +10,10 @@ use roc_mono::layout::{Builtin, Layout}; use crate::code_builder::{BlockType, CodeBuilder, ValueType}; use crate::layout::WasmLayout; +use crate::module_builder::RelocationEntry; +use crate::serialize::SerialBuffer; use crate::storage::{Storage, StoredValue, StoredValueKind}; -use crate::{copy_memory, overwrite_padded_u32, CopyMemoryConfig, Env, LocalId, PTR_TYPE}; +use crate::{copy_memory, CopyMemoryConfig, Env, LocalId, PTR_TYPE}; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. // Follow Emscripten's example by using 1kB (4 bytes would probably do) @@ -26,9 +28,10 @@ pub struct WasmBackend<'a> { // Module-level data pub module_builder: ModuleBuilder, pub code_section_bytes: std::vec::Vec, + pub code_relocations: Vec<'a, RelocationEntry>, _data_offset_map: MutMap, u32>, _data_offset_next: u32, - proc_symbol_map: MutMap, + proc_symbols: &'a [Symbol], // Function-level data code_builder: CodeBuilder<'a>, @@ -40,15 +43,12 @@ pub struct WasmBackend<'a> { } impl<'a> WasmBackend<'a> { - pub fn new(env: &'a Env<'a>, num_procs: usize) -> Self { - // Code section is prefixed with the number of Wasm functions - // For now, this is the same as the number of IR procedures (until we start inlining!) + pub fn new(env: &'a Env<'a>, proc_symbols: &'a [Symbol]) -> Self { let mut code_section_bytes = std::vec::Vec::with_capacity(4096); - // Reserve space for code section header: inner byte length and number of functions - // Padded to the maximum 5 bytes each, so we can update later without moving everything - code_section_bytes.resize(10, 0); - overwrite_padded_u32(&mut code_section_bytes[5..10], num_procs as u32); // gets modified in unit tests + // Code section header + code_section_bytes.reserve_padded_u32(); // byte length, to be written at the end + code_section_bytes.encode_padded_u32(proc_symbols.len() as u32); // modified later in unit tests WasmBackend { env, @@ -58,7 +58,8 @@ impl<'a> WasmBackend<'a> { code_section_bytes, _data_offset_map: MutMap::default(), _data_offset_next: UNUSED_DATA_SECTION_BYTES, - proc_symbol_map: MutMap::default(), + proc_symbols, + code_relocations: Vec::with_capacity_in(256, env.arena), // Function-level data block_depth: 0, @@ -81,7 +82,7 @@ impl<'a> WasmBackend<'a> { ***********************************************************/ - pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result { + pub fn build_proc(&mut self, proc: Proc<'a>, _sym: Symbol) -> Result { // println!("\ngenerating procedure {:?}\n", sym); // Use parity-wasm to add the signature in "types" and "functions" sections @@ -89,7 +90,6 @@ impl<'a> WasmBackend<'a> { let empty_function_def = self.start_proc(&proc); let location = self.module_builder.push_function(empty_function_def); let function_index = location.body; - self.proc_symbol_map.insert(sym, location); self.build_stmt(&proc.body, &proc.ret_layout)?; @@ -141,9 +141,8 @@ impl<'a> WasmBackend<'a> { self.storage.stack_frame_pointer, ); - self.code_builder - .serialize(&mut self.code_section_bytes) - .map_err(|e| format!("{:?}", e))?; + let relocs = self.code_builder.serialize(&mut self.code_section_bytes); + self.code_relocations.extend(relocs); Ok(()) } @@ -396,13 +395,29 @@ impl<'a> WasmBackend<'a> { self.storage.load_symbols(&mut self.code_builder, wasm_args); - let function_location = self.proc_symbol_map.get(func_sym).ok_or(format!( - "Cannot find function {:?} called from {:?}", - func_sym, sym - ))?; + // Index of the called function in the code section + // TODO: account for inlined functions when we start doing that (remember we emit procs out of order) + let func_index = match self.proc_symbols.iter().position(|s| s == func_sym) { + Some(i) => i as u32, + None => { + // TODO: actually useful linking! Push a relocation for it. + return Err(format!( + "Not yet supporteed: calling foreign function {:?}", + func_sym + )); + } + }; + + // Index of the function's name in the symbol table + let symbol_index = func_index; // TODO: update this when we add other things to the symbol table + + self.code_builder.call( + func_index, + symbol_index, + wasm_args.len(), + has_return_val, + ); - self.code_builder - .call(function_location.body, wasm_args.len(), has_return_val); Ok(()) } diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index 09aa6a58a3..fe9319172f 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -1,17 +1,14 @@ -use bumpalo::collections::Vec; +use bumpalo::collections::vec::{Drain, Vec}; use bumpalo::Bump; use core::panic; use std::fmt::Debug; use roc_module::symbol::Symbol; -use crate::{ - encode_f32, encode_f64, encode_i32, encode_i64, encode_u32, round_up_to_alignment, LocalId, - FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID, -}; -use crate::{opcodes::*, overwrite_padded_u32}; - -const DEBUG_LOG: bool = false; +use crate::module_builder::{IndexRelocType, RelocationEntry}; +use crate::opcodes::*; +use crate::serialize::SerialBuffer; +use crate::{round_up_to_alignment, LocalId, FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID}; /// Wasm value type. (Rust representation matches Wasm encoding) #[repr(u8)] @@ -143,6 +140,10 @@ pub struct CodeBuilder<'a> { /// Our simulation model of the Wasm stack machine /// Keeps track of where Symbol values are in the VM stack vm_stack: Vec<'a, Symbol>, + + /// Linker info to help combine the Roc module with builtin & platform modules, + /// e.g. to modify call instructions when function indices change + relocations: Vec<'a, RelocationEntry>, } #[allow(clippy::new_without_default)] @@ -155,6 +156,7 @@ impl<'a> CodeBuilder<'a> { preamble: Vec::with_capacity_in(32, arena), inner_length: Vec::with_capacity_in(5, arena), vm_stack: Vec::with_capacity_in(32, arena), + relocations: Vec::with_capacity_in(32, arena), } } @@ -216,7 +218,7 @@ impl<'a> CodeBuilder<'a> { let start = self.insert_bytes.len(); self.insert_bytes.push(opcode); - encode_u32(&mut self.insert_bytes, immediate); + self.insert_bytes.encode_u32(immediate); self.insert_locations.push(InsertLocation { insert_at, @@ -313,14 +315,14 @@ impl<'a> CodeBuilder<'a> { if *t == batch_type { batch_size += 1; } else { - encode_u32(&mut self.preamble, batch_size); + self.preamble.encode_u32(batch_size); self.preamble.push(batch_type as u8); batch_type = *t; batch_size = 1; num_batches += 1; } } - encode_u32(&mut self.preamble, batch_size); + self.preamble.encode_u32(batch_size); self.preamble.push(batch_type as u8); num_batches += 1; @@ -333,7 +335,7 @@ impl<'a> CodeBuilder<'a> { let old_len = self.preamble.len(); self.preamble.resize(old_len + 4, 0); self.preamble.copy_within(1..old_len, 5); - overwrite_padded_u32(&mut self.preamble[0..5], num_batches); + self.preamble.overwrite_padded_u32(0, num_batches); } } @@ -342,14 +344,14 @@ impl<'a> CodeBuilder<'a> { // Can't use the usual instruction methods because they push to self.code. // This is the only case where we push instructions somewhere different. self.preamble.push(GETGLOBAL); - encode_u32(&mut self.preamble, STACK_POINTER_GLOBAL_ID); + self.preamble.encode_u32(STACK_POINTER_GLOBAL_ID); self.preamble.push(I32CONST); - encode_i32(&mut self.preamble, frame_size); + self.preamble.encode_i32(frame_size); self.preamble.push(I32SUB); self.preamble.push(TEELOCAL); - encode_u32(&mut self.preamble, frame_pointer.0); + self.preamble.encode_u32(frame_pointer.0); self.preamble.push(SETGLOBAL); - encode_u32(&mut self.preamble, STACK_POINTER_GLOBAL_ID); + self.preamble.encode_u32(STACK_POINTER_GLOBAL_ID); } /// Generate instruction bytes to release a frame of stack memory on leaving the function @@ -381,27 +383,53 @@ impl<'a> CodeBuilder<'a> { self.code.push(END); let inner_len = self.preamble.len() + self.code.len() + self.insert_bytes.len(); - encode_u32(&mut self.inner_length, inner_len as u32); + self.inner_length.encode_u32(inner_len as u32); } /// Write out all the bytes in the right order - pub fn serialize(&mut self, writer: &mut W) -> std::io::Result<()> { - writer.write_all(&self.inner_length)?; - writer.write_all(&self.preamble)?; + pub fn serialize( + &mut self, + code_section_buf: &mut T, + ) -> Drain { + code_section_buf.append_slice(&self.inner_length); + code_section_buf.append_slice(&self.preamble); - // We created each insertion when a local was used for the _second_ time. - // But we want them in the order they were first assigned, which may not be the same. + // Sort insertions. They are not created in order of assignment, but in order of *second* usage. self.insert_locations.sort_by_key(|loc| loc.insert_at); - let mut pos: usize = 0; + // Do the insertions & update relocation offsets + const CODE_SECTION_BODY_OFFSET: usize = 5; + let mut reloc_index = 0; + let mut code_pos: usize = 0; for location in self.insert_locations.iter() { - writer.write_all(&self.code[pos..location.insert_at])?; - writer.write_all(&self.insert_bytes[location.start..location.end])?; - pos = location.insert_at; + // Relocation offset needs to be an index into the body of the code section, but + // at this point it is an index into self.code. Need to adjust for all previous functions + // in the code section, and for insertions in the current function. + let section_body_pos = code_section_buf.size() - CODE_SECTION_BODY_OFFSET; + while reloc_index < self.relocations.len() + && self.relocations[reloc_index].offset() < location.insert_at as u32 + { + let offset_ref = self.relocations[reloc_index].offset_mut(); + *offset_ref += (section_body_pos - code_pos) as u32; + reloc_index += 1; + } + + code_section_buf.append_slice(&self.code[code_pos..location.insert_at]); + code_section_buf.append_slice(&self.insert_bytes[location.start..location.end]); + code_pos = location.insert_at; + } + + let section_body_pos = code_section_buf.size() - CODE_SECTION_BODY_OFFSET; + while reloc_index < self.relocations.len() { + let offset_ref = self.relocations[reloc_index].offset_mut(); + *offset_ref += (section_body_pos - code_pos) as u32; + reloc_index += 1; } let len = self.code.len(); - writer.write_all(&self.code[pos..len]) + code_section_buf.append_slice(&self.code[code_pos..len]); + + self.relocations.drain(0..) } /********************************************************** @@ -426,15 +454,16 @@ impl<'a> CodeBuilder<'a> { self.code.push(immediate); } - fn inst_imm32(&mut self, opcode: u8, pops: usize, push: bool, immediate: u32) { + // public for use in test code + pub fn inst_imm32(&mut self, opcode: u8, pops: usize, push: bool, immediate: u32) { self.inst(opcode, pops, push); - encode_u32(&mut self.code, immediate); + self.code.encode_u32(immediate); } fn inst_mem(&mut self, opcode: u8, pops: usize, push: bool, align: Align, offset: u32) { self.inst(opcode, pops, push); self.code.push(align as u8); - encode_u32(&mut self.code, offset); + self.code.encode_u32(offset); } /********************************************************** @@ -469,13 +498,20 @@ impl<'a> CodeBuilder<'a> { pub fn br_if(&mut self, levels: u32) { self.inst_imm32(BRIF, 1, false, levels); } + #[allow(dead_code)] fn br_table() { panic!("TODO"); } instruction_no_args!(return_, RETURN, 0, false); - pub fn call(&mut self, function_index: u32, n_args: usize, has_return_val: bool) { + pub fn call( + &mut self, + function_index: u32, + symbol_index: u32, + n_args: usize, + has_return_val: bool, + ) { let stack_depth = self.vm_stack.len(); if n_args > stack_depth { panic!( @@ -488,8 +524,21 @@ impl<'a> CodeBuilder<'a> { self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE); } self.code.push(CALL); - encode_u32(&mut self.code, function_index); + + // Write the index of the function to be called. + // Also make a RelocationEntry so the linker can see that this byte offset relates to a function by name. + // Here we initialise the offset to an index of self.code. After completing the function, we'll add + // other factors to make it relative to the code section. (All insertions will be known then.) + let offset = self.code.len() as u32; + self.code.encode_padded_u32(function_index); + self.relocations.push(RelocationEntry::Index { + type_id: IndexRelocType::FunctionIndexLeb, + offset, + symbol_index, + }); } + + #[allow(dead_code)] fn call_indirect() { panic!("Not implemented. Roc doesn't use function pointers"); } @@ -545,19 +594,19 @@ impl<'a> CodeBuilder<'a> { } pub fn i32_const(&mut self, x: i32) { self.inst(I32CONST, 0, true); - encode_i32(&mut self.code, x); + self.code.encode_i32(x); } pub fn i64_const(&mut self, x: i64) { self.inst(I64CONST, 0, true); - encode_i64(&mut self.code, x); + self.code.encode_i64(x); } pub fn f32_const(&mut self, x: f32) { self.inst(F32CONST, 0, true); - encode_f32(&mut self.code, x); + self.code.encode_f32(x); } pub fn f64_const(&mut self, x: f64) { self.inst(F64CONST, 0, true); - encode_f64(&mut self.code, x); + self.code.encode_f64(x); } // TODO: Consider creating unified methods for numerical ops like 'eq' and 'add', diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 078102dcfe..1ac84e25b5 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -1,14 +1,12 @@ mod backend; +pub mod code_builder; pub mod from_wasm32_memory; mod layout; +pub mod module_builder; +pub mod opcodes; +pub mod serialize; mod storage; -#[allow(dead_code)] -pub mod code_builder; - -#[allow(dead_code)] -mod opcodes; - use bumpalo::{self, collections::Vec, Bump}; use parity_wasm::builder; @@ -20,6 +18,10 @@ use roc_mono::layout::LayoutIds; use crate::backend::WasmBackend; use crate::code_builder::{Align, CodeBuilder, ValueType}; +use crate::module_builder::{ + LinkingSection, LinkingSubSection, RelocationSection, SectionId, SymInfo, +}; +use crate::serialize::{SerialBuffer, Serialize}; const PTR_SIZE: u32 = 4; const PTR_TYPE: ValueType = ValueType::I32; @@ -44,7 +46,7 @@ pub fn build_module<'a>( env: &'a Env, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) -> Result, String> { - let (builder, code_section_bytes, _) = build_module_help(env, procedures)?; + let (builder, code_section_bytes) = build_module_help(env, procedures)?; let mut module = builder.build(); replace_code_section(&mut module, code_section_bytes); @@ -56,26 +58,20 @@ pub fn build_module<'a>( pub fn build_module_help<'a>( env: &'a Env, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, -) -> Result<(builder::ModuleBuilder, std::vec::Vec, u32), String> { - let mut backend = WasmBackend::new(env, procedures.len()); +) -> Result<(builder::ModuleBuilder, std::vec::Vec), String> { + let proc_symbols = Vec::from_iter_in(procedures.keys().map(|(sym, _)| *sym), env.arena); + let mut backend = WasmBackend::new(env, &proc_symbols); let mut layout_ids = LayoutIds::default(); + let mut symbol_table_entries = Vec::with_capacity_in(procedures.len(), env.arena); - // Sort procedures by occurrence order - // - // We sort by the "name", but those are interned strings, and the name that is - // interned first will have a lower number. - // - // But, the name that occurs first is always `main` because it is in the (implicit) - // file header. Therefore sorting high to low will put other functions before main - // - // This means that for now other functions in the file have to be ordered "in reverse": if A - // uses B, then the name of A must first occur after the first occurrence of the name of B - let mut procedures = Vec::from_iter_in(procedures.into_iter(), env.arena); - procedures.sort_by(|a, b| b.0 .0.cmp(&a.0 .0)); + for (i, ((sym, layout), proc)) in procedures.into_iter().enumerate() { + let proc_name = layout_ids + .get(proc.name, &proc.ret_layout) + .to_symbol_string(proc.name, &env.interns); + symbol_table_entries.push(SymInfo::for_function(i as u32, proc_name)); + + let function_index = backend.build_proc(proc, sym)?; - let mut function_index: u32 = 0; - for ((sym, layout), proc) in procedures { - function_index = backend.build_proc(proc, sym)?; if env.exposed_to_host.contains(&sym) { let fn_name = layout_ids .get_toplevel(sym, &layout) @@ -92,12 +88,42 @@ pub fn build_module_help<'a>( // Update code section length let inner_length = (backend.code_section_bytes.len() - 5) as u32; - overwrite_padded_u32(&mut backend.code_section_bytes[0..5], inner_length); + backend + .code_section_bytes + .overwrite_padded_u32(0, inner_length); - // Because of the sorting above, we know the last function in the `for` is the main function. - // Here we grab its index and return it, so that the test_wrapper is able to call it. - // This is a workaround until we implement object files with symbols and relocations. - let main_function_index = function_index; + // linking metadata section + let mut linking_section_bytes = std::vec::Vec::with_capacity(symbol_table_entries.len() * 20); + let linking_section = LinkingSection { + subsections: bumpalo::vec![in env.arena; + LinkingSubSection::SymbolTable(symbol_table_entries) + ], + }; + linking_section.serialize(&mut linking_section_bytes); + backend.module_builder = backend.module_builder.with_section(Section::Unparsed { + id: SectionId::Custom as u8, + payload: linking_section_bytes, + }); + + // We always output the code section at the same index relative to other sections, and we need that for relocations. + // TODO: If there's a data section, this will be 6 so we'll need logic for that + // TODO: Build a cleaner solution after we replace parity-wasm with our own module_builder + const CODE_SECTION_INDEX: u32 = 5; + + let code_reloc_section = RelocationSection { + name: "reloc.CODE", + target_section_index: CODE_SECTION_INDEX, + entries: &backend.code_relocations, + }; + + let mut code_reloc_section_bytes = std::vec::Vec::with_capacity(256); + code_reloc_section.serialize(&mut code_reloc_section_bytes); + + // Must come after linking section + backend.module_builder = backend.module_builder.with_section(Section::Unparsed { + id: SectionId::Custom as u8, + payload: code_reloc_section_bytes, + }); const MIN_MEMORY_SIZE_KB: u32 = 1024; const PAGE_SIZE_KB: u32 = 64; @@ -119,24 +145,20 @@ pub fn build_module_help<'a>( .build(); backend.module_builder.push_global(stack_pointer_global); - Ok(( - backend.module_builder, - backend.code_section_bytes, - main_function_index, - )) + Ok((backend.module_builder, backend.code_section_bytes)) } /// Replace parity-wasm's code section with our own handmade one pub fn replace_code_section(module: &mut Module, code_section_bytes: std::vec::Vec) { let sections = module.sections_mut(); - let mut code_section_index = usize::MAX; - for (i, s) in sections.iter().enumerate() { - if let Section::Code(_) = s { - code_section_index = i; - } - } + + let code_section_index = sections + .iter() + .position(|s| matches!(s, Section::Code(_))) + .unwrap(); + sections[code_section_index] = Section::Unparsed { - id: CODE_SECTION_ID, + id: SectionId::Code as u8, payload: code_section_bytes, }; } @@ -192,192 +214,3 @@ pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 { pub fn debug_panic(error: E) { panic!("{:?}", error); } - -/// Write an unsigned value into the provided buffer in LEB-128 format, returning byte length -/// -/// All integers in Wasm are variable-length encoded, which saves space for small values. -/// The most significant bit indicates "more bytes are coming", and the other 7 are payload. -macro_rules! encode_uleb128 { - ($name: ident, $ty: ty) => { - pub fn $name<'a>(buffer: &mut Vec<'a, u8>, value: $ty) -> usize { - let mut x = value; - let start_len = buffer.len(); - while x >= 0x80 { - buffer.push(0x80 | ((x & 0x7f) as u8)); - x >>= 7; - } - buffer.push(x as u8); - buffer.len() - start_len - } - }; -} - -encode_uleb128!(encode_u32, u32); -encode_uleb128!(encode_u64, u64); - -/// Write a *signed* value into the provided buffer in LEB-128 format, returning byte length -macro_rules! encode_sleb128 { - ($name: ident, $ty: ty) => { - pub fn $name<'a>(buffer: &mut Vec<'a, u8>, value: $ty) -> usize { - let mut x = value; - let start_len = buffer.len(); - loop { - let byte = (x & 0x7f) as u8; - x >>= 7; - let byte_is_negative = (byte & 0x40) != 0; - if ((x == 0 && !byte_is_negative) || (x == -1 && byte_is_negative)) { - buffer.push(byte); - break; - } - buffer.push(byte | 0x80); - } - buffer.len() - start_len - } - }; -} - -encode_sleb128!(encode_i32, i32); -encode_sleb128!(encode_i64, i64); - -/// No LEB encoding, and always little-endian regardless of compiler host. -macro_rules! encode_float { - ($name: ident, $ty: ty) => { - pub fn $name<'a>(buffer: &mut Vec<'a, u8>, value: $ty) { - let mut x = value.to_bits(); - let size = std::mem::size_of::<$ty>(); - for _ in 0..size { - buffer.push((x & 0xff) as u8); - x >>= 8; - } - } - }; -} - -encode_float!(encode_f32, f32); -encode_float!(encode_f64, f64); - -/// Overwrite a LEB-128 encoded u32 value, padded to maximum length (5 bytes) -/// -/// We need some fixed-length values so we can overwrite them without moving all following bytes. -/// Many parts of the binary format are prefixed with their length, which we only know at the end. -/// And relocation values get updated during linking. -/// This can help us to avoid copies, which is good for speed, but there's a tradeoff with output size. -/// -/// The value 3 is encoded as 0x83 0x80 0x80 0x80 0x00. -/// https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md#relocation-sections -pub fn overwrite_padded_u32(buffer: &mut [u8], value: u32) { - let mut x = value; - for byte in buffer.iter_mut().take(4) { - *byte = 0x80 | ((x & 0x7f) as u8); - x >>= 7; - } - buffer[4] = x as u8; -} - -#[cfg(test)] -mod tests { - use super::*; - use bumpalo::{self, collections::Vec, Bump}; - - fn help_u32<'a>(arena: &'a Bump, value: u32) -> Vec<'a, u8> { - let mut buffer = Vec::with_capacity_in(5, arena); - encode_u32(&mut buffer, value); - buffer - } - - #[test] - fn test_encode_u32() { - let a = &Bump::new(); - assert_eq!(help_u32(a, 0), &[0]); - assert_eq!(help_u32(a, 64), &[64]); - assert_eq!(help_u32(a, 0x7f), &[0x7f]); - assert_eq!(help_u32(a, 0x80), &[0x80, 0x01]); - assert_eq!(help_u32(a, 0x3fff), &[0xff, 0x7f]); - assert_eq!(help_u32(a, 0x4000), &[0x80, 0x80, 0x01]); - assert_eq!(help_u32(a, u32::MAX), &[0xff, 0xff, 0xff, 0xff, 0x0f]); - } - - fn help_u64<'a>(arena: &'a Bump, value: u64) -> Vec<'a, u8> { - let mut buffer = Vec::with_capacity_in(10, arena); - encode_u64(&mut buffer, value); - buffer - } - - #[test] - fn test_encode_u64() { - let a = &Bump::new(); - assert_eq!(help_u64(a, 0), &[0]); - assert_eq!(help_u64(a, 64), &[64]); - assert_eq!(help_u64(a, 0x7f), &[0x7f]); - assert_eq!(help_u64(a, 0x80), &[0x80, 0x01]); - assert_eq!(help_u64(a, 0x3fff), &[0xff, 0x7f]); - assert_eq!(help_u64(a, 0x4000), &[0x80, 0x80, 0x01]); - assert_eq!( - help_u64(a, u64::MAX), - &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01], - ); - } - - fn help_i32<'a>(arena: &'a Bump, value: i32) -> Vec<'a, u8> { - let mut buffer = Vec::with_capacity_in(5, arena); - encode_i32(&mut buffer, value); - buffer - } - - #[test] - fn test_encode_i32() { - let a = &Bump::new(); - assert_eq!(help_i32(a, 0), &[0]); - assert_eq!(help_i32(a, 1), &[1]); - assert_eq!(help_i32(a, -1), &[0x7f]); - assert_eq!(help_i32(a, 63), &[63]); - assert_eq!(help_i32(a, 64), &[0xc0, 0x0]); - assert_eq!(help_i32(a, -64), &[0x40]); - assert_eq!(help_i32(a, -65), &[0xbf, 0x7f]); - assert_eq!(help_i32(a, i32::MAX), &[0xff, 0xff, 0xff, 0xff, 0x07]); - assert_eq!(help_i32(a, i32::MIN), &[0x80, 0x80, 0x80, 0x80, 0x78]); - } - - fn help_i64<'a>(arena: &'a Bump, value: i64) -> Vec<'a, u8> { - let mut buffer = Vec::with_capacity_in(10, arena); - encode_i64(&mut buffer, value); - buffer - } - - #[test] - fn test_encode_i64() { - let a = &Bump::new(); - assert_eq!(help_i64(a, 0), &[0]); - assert_eq!(help_i64(a, 1), &[1]); - assert_eq!(help_i64(a, -1), &[0x7f]); - assert_eq!(help_i64(a, 63), &[63]); - assert_eq!(help_i64(a, 64), &[0xc0, 0x0]); - assert_eq!(help_i64(a, -64), &[0x40]); - assert_eq!(help_i64(a, -65), &[0xbf, 0x7f]); - assert_eq!( - help_i64(a, i64::MAX), - &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00], - ); - assert_eq!( - help_i64(a, i64::MIN), - &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f], - ); - } - - #[test] - fn test_overwrite_u32_padded() { - let mut buffer = [0, 0, 0, 0, 0]; - - overwrite_padded_u32(&mut buffer, u32::MAX); - assert_eq!(buffer, [0xff, 0xff, 0xff, 0xff, 0x0f]); - - overwrite_padded_u32(&mut buffer, 0); - assert_eq!(buffer, [0x80, 0x80, 0x80, 0x80, 0x00]); - - overwrite_padded_u32(&mut buffer, 127); - assert_eq!(buffer, [0xff, 0x80, 0x80, 0x80, 0x00]); - - overwrite_padded_u32(&mut buffer, 128); - assert_eq!(buffer, [0x80, 0x81, 0x80, 0x80, 0x00]); - } -} diff --git a/compiler/gen_wasm/src/module_builder.rs b/compiler/gen_wasm/src/module_builder.rs new file mode 100644 index 0000000000..a0b085343b --- /dev/null +++ b/compiler/gen_wasm/src/module_builder.rs @@ -0,0 +1,499 @@ +use bumpalo::collections::vec::Vec; + +use crate::code_builder::Align; +use crate::serialize::{SerialBuffer, Serialize}; + +#[repr(u8)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum SectionId { + Custom = 0, + Type = 1, + Import = 2, + Function = 3, + Table = 4, + Memory = 5, + Global = 6, + Export = 7, + Start = 8, + Element = 9, + Code = 10, + Data = 11, + DataCount = 12, +} + +struct SectionHeaderIndices { + size_index: usize, + body_index: usize, +} + +/// Write a section header, returning the position of the encoded length +fn _write_section_header(buffer: &mut T, id: SectionId) -> SectionHeaderIndices { + buffer.append_byte(id as u8); + let size_index = buffer.reserve_padded_u32(); + let body_index = buffer.size(); + SectionHeaderIndices { + size_index, + body_index, + } +} + +/// Write a custom section header, returning the position of the encoded length +fn write_custom_section_header( + buffer: &mut T, + name: &str, +) -> SectionHeaderIndices { + // buffer.append_byte(SectionId::Custom as u8); // TODO: uncomment when we get rid of parity_wasm + let size_index = buffer.reserve_padded_u32(); + let body_index = buffer.size(); + name.serialize(buffer); + SectionHeaderIndices { + size_index, + body_index, + } +} + +/// Update a section header with its final size, after writing the bytes +fn update_section_size(buffer: &mut T, header_indices: SectionHeaderIndices) { + let size = buffer.size() - header_indices.body_index; + buffer.overwrite_padded_u32(header_indices.size_index, size as u32); +} + +fn serialize_vector_with_count<'a, SB, S>(buffer: &mut SB, items: &Vec<'a, S>) +where + SB: SerialBuffer, + S: Serialize, +{ + buffer.encode_u32(items.len() as u32); + for item in items.iter() { + item.serialize(buffer); + } +} + +/******************************************************************* + * + * Relocation sections + * + * https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md#relocation-sections + * + *******************************************************************/ + +#[repr(u8)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum IndexRelocType { + /// a function index encoded as a 5-byte [varuint32]. Used for the immediate argument of a `call` instruction. + FunctionIndexLeb = 0, + /// a function table index encoded as a 5-byte [varint32]. + /// Used to refer to the immediate argument of a `i32.const` instruction, e.g. taking the address of a function. + TableIndexSleb = 1, + /// a function table index encoded as a [uint32], e.g. taking the address of a function in a static data initializer. + TableIndexI32 = 2, + /// a type index encoded as a 5-byte [varuint32], e.g. the type immediate in a `call_indirect`. + TypeIndexLeb = 6, + /// a global index encoded as a 5-byte [varuint32], e.g. the index immediate in a `get_global`. + GlobalIndexLeb = 7, + /// an event index encoded as a 5-byte [varuint32]. Used for the immediate argument of a `throw` and `if_except` instruction. + EventIndexLeb = 10, + /// a global index encoded as [uint32]. + GlobalIndexI32 = 13, + /// the 64-bit counterpart of `R_WASM_TABLE_INDEX_SLEB`. A function table index encoded as a 10-byte [varint64]. + /// Used to refer to the immediate argument of a `i64.const` instruction, e.g. taking the address of a function in Wasm64. + TableIndexSleb64 = 18, + /// the 64-bit counterpart of `R_WASM_TABLE_INDEX_I32`. + /// A function table index encoded as a [uint64], e.g. taking the address of a function in a static data initializer. + TableIndexI64 = 19, + /// a table number encoded as a 5-byte [varuint32]. Used for the table immediate argument in the table.* instructions. + TableNumberLeb = 20, +} + +#[repr(u8)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum OffsetRelocType { + /// a linear memory index encoded as a 5-byte [varuint32]. + /// Used for the immediate argument of a `load` or `store` instruction, e.g. directly loading from or storing to a C++ global. + MemoryAddrLeb = 3, + /// a linear memory index encoded as a 5-byte [varint32]. + /// Used for the immediate argument of a `i32.const` instruction, e.g. taking the address of a C++ global. + MemoryAddrSleb = 4, + /// a linear memory index encoded as a [uint32], e.g. taking the address of a C++ global in a static data initializer. + MemoryAddrI32 = 5, + /// a byte offset within code section for the specific function encoded as a [uint32]. + /// The offsets start at the actual function code excluding its size field. + FunctionOffsetI32 = 8, + /// a byte offset from start of the specified section encoded as a [uint32]. + SectionOffsetI32 = 9, + /// the 64-bit counterpart of `R_WASM_MEMORY_ADDR_LEB`. A 64-bit linear memory index encoded as a 10-byte [varuint64], + /// Used for the immediate argument of a `load` or `store` instruction on a 64-bit linear memory array. + MemoryAddrLeb64 = 14, + /// the 64-bit counterpart of `R_WASM_MEMORY_ADDR_SLEB`. A 64-bit linear memory index encoded as a 10-byte [varint64]. + /// Used for the immediate argument of a `i64.const` instruction. + MemoryAddrSleb64 = 15, + /// the 64-bit counterpart of `R_WASM_MEMORY_ADDR`. A 64-bit linear memory index encoded as a [uint64], + /// e.g. taking the 64-bit address of a C++ global in a static data initializer. + MemoryAddrI64 = 16, +} + +#[derive(Debug)] +pub enum RelocationEntry { + Index { + type_id: IndexRelocType, + offset: u32, // offset 0 means the next byte after section id and size + symbol_index: u32, // index in symbol table + }, + Offset { + type_id: OffsetRelocType, + offset: u32, // offset 0 means the next byte after section id and size + symbol_index: u32, // index in symbol table + addend: i32, // addend to add to the address + }, +} + +impl RelocationEntry { + pub fn offset(&self) -> u32 { + match self { + Self::Index { offset, .. } => *offset, + Self::Offset { offset, .. } => *offset, + } + } + + pub fn offset_mut(&mut self) -> &mut u32 { + match self { + Self::Index { offset, .. } => offset, + Self::Offset { offset, .. } => offset, + } + } +} + +impl RelocationEntry { + pub fn for_function_call(offset: u32, symbol_index: u32) -> Self { + RelocationEntry::Index { + type_id: IndexRelocType::FunctionIndexLeb, + offset, + symbol_index, + } + } +} + +impl Serialize for RelocationEntry { + fn serialize(&self, buffer: &mut T) { + match self { + Self::Index { + type_id, + offset, + symbol_index, + } => { + buffer.append_byte(*type_id as u8); + buffer.encode_u32(*offset); + buffer.encode_u32(*symbol_index); + } + Self::Offset { + type_id, + offset, + symbol_index, + addend, + } => { + buffer.append_byte(*type_id as u8); + buffer.encode_u32(*offset); + buffer.encode_u32(*symbol_index); + buffer.encode_i32(*addend); + } + } + } +} + +#[derive(Debug)] +pub struct RelocationSection<'a> { + pub name: &'a str, + /// The *index* (not ID!) of the target section in the module + pub target_section_index: u32, + pub entries: &'a Vec<'a, RelocationEntry>, +} + +impl<'a> Serialize for RelocationSection<'a> { + fn serialize(&self, buffer: &mut T) { + let header_indices = write_custom_section_header(buffer, self.name); + buffer.encode_u32(self.target_section_index); + serialize_vector_with_count(buffer, self.entries); + update_section_size(buffer, header_indices); + } +} + +/******************************************************************* + * + * Linking section + * + * https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md#linking-metadata-section + * + *******************************************************************/ + +/// Linking metadata for data segments +pub struct LinkingSegment { + pub name: String, + pub alignment: Align, + pub flags: u32, +} +impl Serialize for LinkingSegment { + fn serialize(&self, _buffer: &mut T) { + todo!(); + } +} + +/// Linking metadata for init (start) functions +pub struct LinkingInitFunc { + pub priority: u32, + pub symbol_index: u32, // index in the symbol table, not the function index +} +impl Serialize for LinkingInitFunc { + fn serialize(&self, _buffer: &mut T) { + todo!(); + } +} + +//---------------- +// +// Common data +// +//---------------- + +#[repr(u8)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum ComdatSymKind { + Data = 0, + Function = 1, + Global = 2, + Event = 3, + Table = 4, + Section = 5, +} + +pub struct ComdatSym { + pub kind: ComdatSymKind, + pub index: u32, +} +impl Serialize for ComdatSym { + fn serialize(&self, _buffer: &mut T) { + todo!(); + } +} + +/// Linking metadata for common data +/// A COMDAT group may contain one or more functions, data segments, and/or custom sections. +/// The linker will include all of these elements with a given group name from one object file, +/// and will exclude any element with this group name from all other object files. +#[allow(dead_code)] +pub struct LinkingComdat<'a> { + name: String, + flags: u32, + syms: Vec<'a, ComdatSym>, +} +impl<'a> Serialize for LinkingComdat<'a> { + fn serialize(&self, _buffer: &mut T) { + todo!(); + } +} + +//---------------- +// +// Symbol table +// +//---------------- + +/// Indicating that this is a weak symbol. When +/// linking multiple modules defining the same symbol, all weak definitions are +/// discarded if any strong definitions exist; then if multiple weak definitions +/// exist all but one (unspecified) are discarded; and finally it is an error if +/// more than one definition remains. +pub const WASM_SYM_BINDING_WEAK: u32 = 1; + +/// Indicating that this is a local symbol (this is exclusive with `WASM_SYM_BINDING_WEAK`). +/// Local symbols are not to be exported, or linked to other modules/sections. +/// The names of all non-local symbols must be unique, but the names of local symbols +/// are not considered for uniqueness. A local function or global symbol cannot reference an import. +pub const WASM_SYM_BINDING_LOCAL: u32 = 2; + +/// Indicating that this is a hidden symbol. +/// Hidden symbols are not to be exported when performing the final link, but +/// may be linked to other modules. +pub const WASM_SYM_VISIBILITY_HIDDEN: u32 = 4; + +/// Indicating that this symbol is not defined. +/// For non-data symbols, this must match whether the symbol is an import +/// or is defined; for data symbols, determines whether a segment is specified. +pub const WASM_SYM_UNDEFINED: u32 = 0x10; // required if the symbol refers to an import + +/// The symbol is intended to be exported from the +/// wasm module to the host environment. This differs from the visibility flags +/// in that it effects the static linker. +pub const WASM_SYM_EXPORTED: u32 = 0x20; + +/// The symbol uses an explicit symbol name, +/// rather than reusing the name from a wasm import. This allows it to remap +/// imports from foreign WebAssembly modules into local symbols with different +/// names. +pub const WASM_SYM_EXPLICIT_NAME: u32 = 0x40; // use the name from the symbol table, not from the import + +/// The symbol is intended to be included in the +/// linker output, regardless of whether it is used by the program. +pub const WASM_SYM_NO_STRIP: u32 = 0x80; + +pub enum WasmObjectSymbol { + Defined { index: u32, name: String }, + Imported { index: u32 }, +} +impl Serialize for WasmObjectSymbol { + fn serialize(&self, buffer: &mut T) { + match self { + Self::Defined { index, name } => { + buffer.encode_u32(*index); + buffer.encode_u32(name.len() as u32); + buffer.append_slice(name.as_bytes()); + } + Self::Imported { index } => { + buffer.encode_u32(*index); + } + } + } +} + +pub enum DataSymbol { + Defined { + name: String, + index: u32, + offset: u32, + size: u32, + }, + Imported { + name: String, + }, +} +impl Serialize for DataSymbol { + fn serialize(&self, buffer: &mut T) { + match self { + Self::Defined { + name, + index, + offset, + size, + } => { + buffer.encode_u32(name.len() as u32); + buffer.append_slice(name.as_bytes()); + buffer.encode_u32(*index); + buffer.encode_u32(*offset); + buffer.encode_u32(*size); + } + Self::Imported { name } => { + buffer.encode_u32(name.len() as u32); + buffer.append_slice(name.as_bytes()); + } + } + } +} + +/// section index (not section id!) +#[derive(Clone, Copy, Debug)] +pub struct SectionIndex(u32); + +pub enum SymInfoFields { + Function(WasmObjectSymbol), + Data(DataSymbol), + Global(WasmObjectSymbol), + Section(SectionIndex), + Event(WasmObjectSymbol), + Table(WasmObjectSymbol), +} + +pub struct SymInfo { + flags: u32, + info: SymInfoFields, +} +impl SymInfo { + pub fn for_function(wasm_function_index: u32, name: String) -> Self { + let linking_symbol = WasmObjectSymbol::Defined { + index: wasm_function_index, + name, + }; + SymInfo { + flags: 0, + info: SymInfoFields::Function(linking_symbol), + } + } +} + +impl Serialize for SymInfo { + fn serialize(&self, buffer: &mut T) { + buffer.append_byte(match self.info { + SymInfoFields::Function(_) => 0, + SymInfoFields::Data(_) => 1, + SymInfoFields::Global(_) => 2, + SymInfoFields::Section(_) => 3, + SymInfoFields::Event(_) => 4, + SymInfoFields::Table(_) => 5, + }); + buffer.encode_u32(self.flags); + match &self.info { + SymInfoFields::Function(x) => x.serialize(buffer), + SymInfoFields::Data(x) => x.serialize(buffer), + SymInfoFields::Global(x) => x.serialize(buffer), + SymInfoFields::Section(SectionIndex(x)) => { + buffer.encode_u32(*x); + } + SymInfoFields::Event(x) => x.serialize(buffer), + SymInfoFields::Table(x) => x.serialize(buffer), + }; + } +} + +//-------------------------------- +// +// Linking subsections +// +//-------------------------------- + +pub enum LinkingSubSection<'a> { + /// Extra metadata about the data segments. + SegmentInfo(Vec<'a, LinkingSegment>), + /// Specifies a list of constructor functions to be called at startup. + /// These constructors will be called in priority order after memory has been initialized. + InitFuncs(Vec<'a, LinkingInitFunc>), + /// Specifies the COMDAT groups of associated linking objects, which are linked only once and all together. + ComdatInfo(Vec<'a, LinkingComdat<'a>>), + /// Specifies extra information about the symbols present in the module. + SymbolTable(Vec<'a, SymInfo>), +} +impl<'a> Serialize for LinkingSubSection<'a> { + fn serialize(&self, buffer: &mut T) { + buffer.append_byte(match self { + Self::SegmentInfo(_) => 5, + Self::InitFuncs(_) => 6, + Self::ComdatInfo(_) => 7, + Self::SymbolTable(_) => 8, + }); + let payload_len_index = buffer.reserve_padded_u32(); + let payload_start_index = buffer.size(); + match self { + Self::SegmentInfo(items) => serialize_vector_with_count(buffer, items), + Self::InitFuncs(items) => serialize_vector_with_count(buffer, items), + Self::ComdatInfo(items) => serialize_vector_with_count(buffer, items), + Self::SymbolTable(items) => serialize_vector_with_count(buffer, items), + } + buffer.overwrite_padded_u32( + payload_len_index, + (buffer.size() - payload_start_index) as u32, + ); + } +} + +const LINKING_VERSION: u8 = 2; + +pub struct LinkingSection<'a> { + pub subsections: Vec<'a, LinkingSubSection<'a>>, +} +impl<'a> Serialize for LinkingSection<'a> { + fn serialize(&self, buffer: &mut T) { + let header_indices = write_custom_section_header(buffer, "linking"); + buffer.append_byte(LINKING_VERSION); + for subsection in self.subsections.iter() { + subsection.serialize(buffer); + } + update_section_size(buffer, header_indices); + } +} diff --git a/compiler/gen_wasm/src/serialize.rs b/compiler/gen_wasm/src/serialize.rs new file mode 100644 index 0000000000..15e0fab285 --- /dev/null +++ b/compiler/gen_wasm/src/serialize.rs @@ -0,0 +1,350 @@ +use bumpalo::collections::vec::Vec; + +/// Write an unsigned integer into the provided buffer in LEB-128 format, returning byte length +/// +/// All integers in Wasm are variable-length encoded, which saves space for small values. +/// The most significant bit indicates "more bytes are coming", and the other 7 are payload. +macro_rules! encode_uleb128 { + ($name: ident, $ty: ty) => { + fn $name(&mut self, value: $ty) -> usize { + let mut x = value; + let start_len = self.size(); + while x >= 0x80 { + self.append_byte(0x80 | ((x & 0x7f) as u8)); + x >>= 7; + } + self.append_byte(x as u8); + self.size() - start_len + } + }; +} + +/// Write a signed integer into the provided buffer in LEB-128 format, returning byte length +macro_rules! encode_sleb128 { + ($name: ident, $ty: ty) => { + fn $name(&mut self, value: $ty) -> usize { + let mut x = value; + let start_len = self.size(); + loop { + let byte = (x & 0x7f) as u8; + x >>= 7; + let byte_is_negative = (byte & 0x40) != 0; + if ((x == 0 && !byte_is_negative) || (x == -1 && byte_is_negative)) { + self.append_byte(byte); + break; + } + self.append_byte(byte | 0x80); + } + self.size() - start_len + } + }; +} + +macro_rules! write_unencoded { + ($name: ident, $ty: ty) => { + /// write an unencoded little-endian integer (only used in relocations) + fn $name(&mut self, value: $ty) { + let mut x = value; + let size = std::mem::size_of::<$ty>(); + for _ in 0..size { + self.append_byte((x & 0xff) as u8); + x >>= 8; + } + } + }; +} + +macro_rules! encode_padded_sleb128 { + ($name: ident, $ty: ty) => { + /// write a maximally-padded SLEB128 integer (only used in relocations) + fn $name(&mut self, value: $ty) { + let mut x = value; + let size = (std::mem::size_of::<$ty>() / 4) * 5; + for _ in 0..(size - 1) { + self.append_byte(0x80 | (x & 0x7f) as u8); + x >>= 7; + } + self.append_byte((x & 0x7f) as u8); + } + }; +} + +pub trait SerialBuffer { + fn append_byte(&mut self, b: u8); + fn append_slice(&mut self, b: &[u8]); + fn size(&self) -> usize; + + encode_uleb128!(encode_u32, u32); + encode_uleb128!(encode_u64, u64); + encode_sleb128!(encode_i32, i32); + encode_sleb128!(encode_i64, i64); + + fn reserve_padded_u32(&mut self) -> usize; + fn encode_padded_u32(&mut self, value: u32) -> usize; + fn overwrite_padded_u32(&mut self, index: usize, value: u32); + + fn encode_f32(&mut self, value: f32) { + self.write_unencoded_u32(value.to_bits()); + } + + fn encode_f64(&mut self, value: f64) { + self.write_unencoded_u64(value.to_bits()); + } + + // methods for relocations + write_unencoded!(write_unencoded_u32, u32); + write_unencoded!(write_unencoded_u64, u64); + encode_padded_sleb128!(encode_padded_i32, i32); + encode_padded_sleb128!(encode_padded_i64, i64); +} + +pub trait Serialize { + fn serialize(&self, buffer: &mut T); +} + +impl Serialize for str { + fn serialize(&self, buffer: &mut T) { + buffer.encode_u32(self.len() as u32); + buffer.append_slice(self.as_bytes()); + } +} + +fn overwrite_padded_u32_help(buffer: &mut [u8], value: u32) { + let mut x = value; + for byte in buffer.iter_mut().take(4) { + *byte = 0x80 | ((x & 0x7f) as u8); + x >>= 7; + } + buffer[4] = x as u8; +} + +impl SerialBuffer for std::vec::Vec { + fn append_byte(&mut self, b: u8) { + self.push(b); + } + fn append_slice(&mut self, b: &[u8]) { + self.extend_from_slice(b); + } + fn size(&self) -> usize { + self.len() + } + fn reserve_padded_u32(&mut self) -> usize { + let index = self.len(); + self.resize(index + 5, 0xff); + index + } + fn encode_padded_u32(&mut self, value: u32) -> usize { + let index = self.len(); + let new_len = index + 5; + self.resize(new_len, 0); + overwrite_padded_u32_help(&mut self[index..new_len], value); + index + } + fn overwrite_padded_u32(&mut self, index: usize, value: u32) { + overwrite_padded_u32_help(&mut self[index..(index + 5)], value); + } +} + +impl<'a> SerialBuffer for Vec<'a, u8> { + fn append_byte(&mut self, b: u8) { + self.push(b); + } + fn append_slice(&mut self, b: &[u8]) { + self.extend_from_slice(b); + } + fn size(&self) -> usize { + self.len() + } + fn reserve_padded_u32(&mut self) -> usize { + let index = self.len(); + self.resize(index + 5, 0xff); + index + } + fn encode_padded_u32(&mut self, value: u32) -> usize { + let index = self.len(); + let new_len = index + 5; + self.resize(new_len, 0); + overwrite_padded_u32_help(&mut self[index..new_len], value); + index + } + fn overwrite_padded_u32(&mut self, index: usize, value: u32) { + overwrite_padded_u32_help(&mut self[index..(index + 5)], value); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bumpalo::{self, collections::Vec, Bump}; + + fn help_u32<'a>(arena: &'a Bump, value: u32) -> Vec<'a, u8> { + let mut buffer = Vec::with_capacity_in(5, arena); + buffer.encode_u32(value); + buffer + } + + #[test] + fn test_encode_u32() { + let a = &Bump::new(); + assert_eq!(help_u32(a, 0), &[0]); + assert_eq!(help_u32(a, 64), &[64]); + assert_eq!(help_u32(a, 0x7f), &[0x7f]); + assert_eq!(help_u32(a, 0x80), &[0x80, 0x01]); + assert_eq!(help_u32(a, 0x3fff), &[0xff, 0x7f]); + assert_eq!(help_u32(a, 0x4000), &[0x80, 0x80, 0x01]); + assert_eq!(help_u32(a, u32::MAX), &[0xff, 0xff, 0xff, 0xff, 0x0f]); + } + + fn help_u64<'a>(arena: &'a Bump, value: u64) -> Vec<'a, u8> { + let mut buffer = Vec::with_capacity_in(10, arena); + buffer.encode_u64(value); + buffer + } + + #[test] + fn test_encode_u64() { + let a = &Bump::new(); + assert_eq!(help_u64(a, 0), &[0]); + assert_eq!(help_u64(a, 64), &[64]); + assert_eq!(help_u64(a, 0x7f), &[0x7f]); + assert_eq!(help_u64(a, 0x80), &[0x80, 0x01]); + assert_eq!(help_u64(a, 0x3fff), &[0xff, 0x7f]); + assert_eq!(help_u64(a, 0x4000), &[0x80, 0x80, 0x01]); + assert_eq!( + help_u64(a, u64::MAX), + &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01], + ); + } + + fn help_i32<'a>(arena: &'a Bump, value: i32) -> Vec<'a, u8> { + let mut buffer = Vec::with_capacity_in(5, arena); + buffer.encode_i32(value); + buffer + } + + #[test] + fn test_encode_i32() { + let a = &Bump::new(); + assert_eq!(help_i32(a, 0), &[0]); + assert_eq!(help_i32(a, 1), &[1]); + assert_eq!(help_i32(a, -1), &[0x7f]); + assert_eq!(help_i32(a, 63), &[63]); + assert_eq!(help_i32(a, 64), &[0xc0, 0x0]); + assert_eq!(help_i32(a, -64), &[0x40]); + assert_eq!(help_i32(a, -65), &[0xbf, 0x7f]); + assert_eq!(help_i32(a, i32::MAX), &[0xff, 0xff, 0xff, 0xff, 0x07]); + assert_eq!(help_i32(a, i32::MIN), &[0x80, 0x80, 0x80, 0x80, 0x78]); + } + + fn help_i64<'a>(arena: &'a Bump, value: i64) -> Vec<'a, u8> { + let mut buffer = Vec::with_capacity_in(10, arena); + buffer.encode_i64(value); + buffer + } + + #[test] + fn test_encode_i64() { + let a = &Bump::new(); + assert_eq!(help_i64(a, 0), &[0]); + assert_eq!(help_i64(a, 1), &[1]); + assert_eq!(help_i64(a, -1), &[0x7f]); + assert_eq!(help_i64(a, 63), &[63]); + assert_eq!(help_i64(a, 64), &[0xc0, 0x0]); + assert_eq!(help_i64(a, -64), &[0x40]); + assert_eq!(help_i64(a, -65), &[0xbf, 0x7f]); + assert_eq!( + help_i64(a, i64::MAX), + &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00], + ); + assert_eq!( + help_i64(a, i64::MIN), + &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f], + ); + } + + #[test] + fn test_overwrite_u32_padded() { + let mut buffer = [0, 0, 0, 0, 0]; + + overwrite_padded_u32_help(&mut buffer, u32::MAX); + assert_eq!(buffer, [0xff, 0xff, 0xff, 0xff, 0x0f]); + + overwrite_padded_u32_help(&mut buffer, 0); + assert_eq!(buffer, [0x80, 0x80, 0x80, 0x80, 0x00]); + + overwrite_padded_u32_help(&mut buffer, 127); + assert_eq!(buffer, [0xff, 0x80, 0x80, 0x80, 0x00]); + + overwrite_padded_u32_help(&mut buffer, 128); + assert_eq!(buffer, [0x80, 0x81, 0x80, 0x80, 0x00]); + } + + #[test] + fn test_write_unencoded_u32() { + let mut buffer = std::vec::Vec::with_capacity(4); + + buffer.write_unencoded_u32(0); + assert_eq!(buffer, &[0, 0, 0, 0]); + + buffer.clear(); + buffer.write_unencoded_u32(u32::MAX); + assert_eq!(buffer, &[0xff, 0xff, 0xff, 0xff]); + } + + #[test] + fn test_write_unencoded_u64() { + let mut buffer = std::vec::Vec::with_capacity(8); + + buffer.write_unencoded_u64(0); + assert_eq!(buffer, &[0, 0, 0, 0, 0, 0, 0, 0]); + + buffer.clear(); + buffer.write_unencoded_u64(u64::MAX); + assert_eq!(buffer, &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]); + } + + fn help_pad_i32(val: i32) -> std::vec::Vec { + let mut buffer = std::vec::Vec::with_capacity(5); + buffer.encode_padded_i32(val); + buffer + } + + #[test] + fn test_encode_padded_i32() { + assert_eq!(help_pad_i32(0), &[0x80, 0x80, 0x80, 0x80, 0x00]); + assert_eq!(help_pad_i32(1), &[0x81, 0x80, 0x80, 0x80, 0x00]); + assert_eq!(help_pad_i32(-1), &[0xff, 0xff, 0xff, 0xff, 0x7f]); + assert_eq!(help_pad_i32(i32::MAX), &[0xff, 0xff, 0xff, 0xff, 0x07]); + assert_eq!(help_pad_i32(i32::MIN), &[0x80, 0x80, 0x80, 0x80, 0x78]); + } + + fn help_pad_i64(val: i64) -> std::vec::Vec { + let mut buffer = std::vec::Vec::with_capacity(10); + buffer.encode_padded_i64(val); + buffer + } + + #[test] + fn test_encode_padded_i64() { + assert_eq!( + help_pad_i64(0), + &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00] + ); + assert_eq!( + help_pad_i64(1), + &[0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00] + ); + assert_eq!( + help_pad_i64(-1), + &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f] + ); + assert_eq!( + help_pad_i64(i64::MAX), + &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00], + ); + assert_eq!( + help_pad_i64(i64::MIN), + &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f], + ); + } +} diff --git a/compiler/gen_wasm/tests/wasm_num.rs b/compiler/gen_wasm/tests/wasm_num.rs deleted file mode 100644 index bc327aa0eb..0000000000 --- a/compiler/gen_wasm/tests/wasm_num.rs +++ /dev/null @@ -1,1063 +0,0 @@ -#[macro_use] -extern crate pretty_assertions; - -#[macro_use] -extern crate indoc; - -extern crate bumpalo; -extern crate libc; - -#[macro_use] -mod helpers; - -#[cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))] -mod wasm_num { - #[test] - fn i64_values() { - assert_evals_to!("0", 0, i64); - assert_evals_to!("-0", 0, i64); - assert_evals_to!("-1", -1, i64); - assert_evals_to!("1", 1, i64); - assert_evals_to!("9_000_000_000_000", 9_000_000_000_000, i64); - assert_evals_to!("-9_000_000_000_000", -9_000_000_000_000, i64); - assert_evals_to!("0b1010", 0b1010, i64); - assert_evals_to!("0o17", 0o17, i64); - assert_evals_to!("0x1000_0000_0000_0000", 0x1000_0000_0000_0000, i64); - } - - #[test] - fn f64_values() { - assert_evals_to!("0.0", 0.0, f64); - assert_evals_to!("-0.0", 0.0, f64); - assert_evals_to!("1.0", 1.0, f64); - assert_evals_to!("-1.0", -1.0, f64); - assert_evals_to!("3.1415926535897932", 3.141_592_653_589_793, f64); - assert_evals_to!(&format!("{:0.1}", f64::MIN), f64::MIN, f64); - assert_evals_to!(&format!("{:0.1}", f64::MAX), f64::MAX, f64); - } - - #[test] - fn i8_add_wrap() { - assert_evals_to!( - indoc!( - r#" - x : I8 - x = 0x7f + 0x7f - - x - "# - ), - -2, - i8 - ); - } - - #[test] - fn i16_add_wrap() { - assert_evals_to!( - indoc!( - r#" - x : I16 - x = 0x7fff + 0x7fff - - x - "# - ), - -2, - i16 - ); - } - - #[test] - fn i32_add_wrap() { - assert_evals_to!( - indoc!( - r#" - x : I32 - x = 0x7fffffff + 0x7fffffff - - x - "# - ), - -2, - i32 - ); - } - - #[test] - fn u8_add_wrap() { - assert_evals_to!( - indoc!( - r#" - x : U8 - x = 0xff + 0xff - - x - "# - ), - 0xfe, - u8 - ); - } - - #[test] - fn u16_add_wrap() { - assert_evals_to!( - indoc!( - r#" - x : U16 - x = 0xffff + 0xffff - - x - "# - ), - 0xfffe, - u16 - ); - } - #[test] - fn u32_add_wrap() { - assert_evals_to!( - indoc!( - r#" - x : U32 - x = 0xffffffff + 0xffffffff - - x - "# - ), - 0xfffffffe, - u32 - ); - } - - #[test] - fn gen_add_i64() { - assert_evals_to!( - indoc!( - r#" - 1 + 2 + 3 - "# - ), - 6, - i64 - ); - } - - #[test] - fn if_then_else() { - assert_evals_to!( - indoc!( - r#" - cond : Bool - cond = True - - if cond then - 0 - else - 1 - "# - ), - 0, - i64 - ); - } - - #[test] - fn rgb_red() { - assert_evals_to!( - indoc!( - r#" - when Red is - Red -> 111 - Green -> 222 - Blue -> 333 - "# - ), - 111, - i64 - ); - } - - #[test] - fn rgb_green() { - assert_evals_to!( - indoc!( - r#" - when Green is - Red -> 111 - Green -> 222 - Blue -> 333 - "# - ), - 222, - i64 - ); - } - - #[test] - fn rgb_blue() { - assert_evals_to!( - indoc!( - r#" - when Blue is - Red -> 111 - Green -> 222 - Blue -> 333 - "# - ), - 333, - i64 - ); - } - - #[test] - fn join_point() { - assert_evals_to!( - indoc!( - r#" - x = if True then 111 else 222 - - x + 123 - "# - ), - 234, - i64 - ); - } - - #[test] - fn factorial() { - assert_evals_to!( - indoc!( - r#" - app "test" provides [ main ] to "./platform" - - fac : I32, I32 -> I32 - fac = \n, accum -> - if n > 1 then - fac (n - 1) (n * accum) - else - accum - - main : I32 - main = fac 8 1 - "# - ), - 40_320, - i32 - ); - } - - #[test] - fn gen_add_f64() { - assert_evals_to!( - indoc!( - r#" - 1.1 + 2.4 + 3 - "# - ), - 6.5, - f64 - ); - } - - #[test] - fn gen_sub_i64() { - assert_evals_to!( - indoc!( - r#" - 1 - 2 - 3 - "# - ), - -4, - i64 - ); - } - - #[test] - fn gen_mul_i64() { - assert_evals_to!( - indoc!( - r#" - 2 * 4 * 6 - "# - ), - 48, - i64 - ); - } - - #[test] - fn i64_force_stack() { - // This claims 33 registers. One more than Arm and RISC-V, and many more than x86-64. - assert_evals_to!( - indoc!( - r#" - a = 0 - b = 1 - c = 2 - d = 3 - e = 4 - f = 5 - g = 6 - h = 7 - i = 8 - j = 9 - k = 10 - l = 11 - m = 12 - n = 13 - o = 14 - p = 15 - q = 16 - r = 17 - s = 18 - t = 19 - u = 20 - v = 21 - w = 22 - x = 23 - y = 24 - z = 25 - aa = 26 - ab = 27 - ac = 28 - ad = 29 - ae = 30 - af = 31 - ag = 32 - - a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + aa + ab + ac + ad + ae + af + ag - "# - ), - 528, - i64 - ); - } - - // #[test] - // fn i64_abs() { - // assert_evals_to!("Num.abs -6", 6, i64); - // assert_evals_to!("Num.abs 7", 7, i64); - // assert_evals_to!("Num.abs 0", 0, i64); - // assert_evals_to!("Num.abs -0", 0, i64); - // assert_evals_to!("Num.abs -1", 1, i64); - // assert_evals_to!("Num.abs 1", 1, i64); - // assert_evals_to!("Num.abs 9_000_000_000_000", 9_000_000_000_000, i64); - // assert_evals_to!("Num.abs -9_000_000_000_000", 9_000_000_000_000, i64); - // } - - // #[test] - // fn gen_int_eq() { - // assert_evals_to!( - // indoc!( - // r#" - // 4 == 4 - // "# - // ), - // true, - // bool - // ); - - // assert_evals_to!( - // indoc!( - // r#" - // 3 == 4 - // "# - // ), - // false, - // bool - // ); - // } - - // #[test] - // fn gen_basic_fn() { - // assert_evals_to!( - // indoc!( - // r#" - // always42 : Num.Num (Num.Integer Num.Signed64) -> Num.Num (Num.Integer Num.Signed64) - // always42 = \_ -> 42 - - // always42 5 - // "# - // ), - // 42, - // i64 - // ); - // } - - // #[test] - // fn gen_wrap_add_nums() { - // assert_evals_to!( - // indoc!( - // r#" - // add2 = \num1, num2 -> num1 + num2 - - // add2 4 5 - // "# - // ), - // 9, - // i64 - // ); - // } - - // #[test] - // fn gen_wrap_add_nums_force_stack() { - // assert_evals_to!( - // indoc!( - // r#" - // add9 = \num1, num2, num3, num4, num5, num6, num7, num8, num9 -> num1 + num2 + num3 + num4 + num5 + num6 + num7 + num8 + num9 - - // add9 1 2 3 4 5 6 7 8 9 - // "# - // ), - // 45, - // i64 - // ); - // } - - // #[test] - // fn pow_int() { - // assert_evals_to!("Num.powInt 2 3", 8, i64); - // } - - // #[test] - // fn acos() { - // assert_evals_to!("Num.acos 0.5", 1.0471975511965979, f64); - // } - - // #[test] - // fn asin() { - // assert_evals_to!("Num.asin 0.5", 0.5235987755982989, f64); - // } - - // #[test] - // fn atan() { - // assert_evals_to!("Num.atan 10", 1.4711276743037347, f64); - // } - - // #[test] - // fn gen_if_fn() { - // assert_evals_to!( - // indoc!( - // r#" - // limitedNegate = \num -> - // x = - // if num == 1 then - // -1 - // else if num == -1 then - // 1 - // else - // num - // x - - // limitedNegate 1 - // "# - // ), - // -1, - // i64 - // ); - // } - - // #[test] - // fn gen_fib_fn() { - // assert_evals_to!( - // indoc!( - // r#" - // fib = \n -> - // if n == 0 then - // 0 - // else if n == 1 then - // 1 - // else - // (fib (n - 1)) + (fib (n - 2)) - - // fib 10 - // "# - // ), - // 55, - // i64 - // ); - // } - - // #[test] - // fn f64_abs() { - // assert_evals_to!("Num.abs -4.7", 4.7, f64); - // assert_evals_to!("Num.abs 5.8", 5.8, f64); - // } - - // #[test] - // fn f64_round() { - // assert_evals_to!("Num.round 3.6", 4, i64); - // assert_evals_to!("Num.round 3.4", 3, i64); - // assert_evals_to!("Num.round 2.5", 3, i64); - // assert_evals_to!("Num.round -2.3", -2, i64); - // assert_evals_to!("Num.round -2.5", -3, i64); - // } - - // #[test] - // fn f64_sqrt() { - // // FIXME this works with normal types, but fails when checking uniqueness types - // assert_evals_to!( - // indoc!( - // r#" - // when Num.sqrt 100 is - // Ok val -> val - // Err _ -> -1 - // "# - // ), - // 10.0, - // f64 - // ); - // } - - // #[test] - // fn gen_float_eq() { - // assert_evals_to!( - // indoc!( - // r#" - // 1.0 == 1.0 - // "# - // ), - // true, - // bool - // ); - // } - - // #[test] - // fn gen_div_f64() { - // // FIXME this works with normal types, but fails when checking uniqueness types - // assert_evals_to!( - // indoc!( - // r#" - // when 48 / 2 is - // Ok val -> val - // Err _ -> -1 - // "# - // ), - // 24.0, - // f64 - // ); - // } - - // #[test] - // fn gen_int_neq() { - // assert_evals_to!( - // indoc!( - // r#" - // 4 != 5 - // "# - // ), - // true, - // bool - // ); - // } - - // #[test] - // fn gen_wrap_int_neq() { - // assert_evals_to!( - // indoc!( - // r#" - // wrappedNotEq : a, a -> Bool - // wrappedNotEq = \num1, num2 -> - // num1 != num2 - - // wrappedNotEq 2 3 - // "# - // ), - // true, - // bool - // ); - // } - - #[test] - fn gen_sub_f64() { - assert_evals_to!( - indoc!( - r#" - 1.5 - 2.4 - 3 - "# - ), - -3.9, - f64 - ); - } - - // #[test] - // fn gen_div_i64() { - // assert_evals_to!( - // indoc!( - // r#" - // when 1000 // 10 is - // Ok val -> val - // Err _ -> -1 - // "# - // ), - // 100, - // i64 - // ); - // } - - // #[test] - // fn gen_div_by_zero_i64() { - // assert_evals_to!( - // indoc!( - // r#" - // when 1000 // 0 is - // Err DivByZero -> 99 - // _ -> -24 - // "# - // ), - // 99, - // i64 - // ); - // } - - // #[test] - // fn gen_rem_i64() { - // assert_evals_to!( - // indoc!( - // r#" - // when Num.rem 8 3 is - // Ok val -> val - // Err _ -> -1 - // "# - // ), - // 2, - // i64 - // ); - // } - - // #[test] - // fn gen_rem_div_by_zero_i64() { - // assert_evals_to!( - // indoc!( - // r#" - // when Num.rem 8 0 is - // Err DivByZero -> 4 - // Ok _ -> -23 - // "# - // ), - // 4, - // i64 - // ); - // } - - // #[test] - // fn gen_is_zero_i64() { - // assert_evals_to!("Num.isZero 0", true, bool); - // assert_evals_to!("Num.isZero 1", false, bool); - // } - - // #[test] - // fn gen_is_positive_i64() { - // assert_evals_to!("Num.isPositive 0", false, bool); - // assert_evals_to!("Num.isPositive 1", true, bool); - // assert_evals_to!("Num.isPositive -5", false, bool); - // } - - // #[test] - // fn gen_is_negative_i64() { - // assert_evals_to!("Num.isNegative 0", false, bool); - // assert_evals_to!("Num.isNegative 3", false, bool); - // assert_evals_to!("Num.isNegative -2", true, bool); - // } - - // #[test] - // fn gen_is_positive_f64() { - // assert_evals_to!("Num.isPositive 0.0", false, bool); - // assert_evals_to!("Num.isPositive 4.7", true, bool); - // assert_evals_to!("Num.isPositive -8.5", false, bool); - // } - - // #[test] - // fn gen_is_negative_f64() { - // assert_evals_to!("Num.isNegative 0.0", false, bool); - // assert_evals_to!("Num.isNegative 9.9", false, bool); - // assert_evals_to!("Num.isNegative -4.4", true, bool); - // } - - // #[test] - // fn gen_is_zero_f64() { - // assert_evals_to!("Num.isZero 0", true, bool); - // assert_evals_to!("Num.isZero 0_0", true, bool); - // assert_evals_to!("Num.isZero 0.0", true, bool); - // assert_evals_to!("Num.isZero 1", false, bool); - // } - - // #[test] - // fn gen_is_odd() { - // assert_evals_to!("Num.isOdd 4", false, bool); - // assert_evals_to!("Num.isOdd 5", true, bool); - // } - - // #[test] - // fn gen_is_even() { - // assert_evals_to!("Num.isEven 6", true, bool); - // assert_evals_to!("Num.isEven 7", false, bool); - // } - - // #[test] - // fn sin() { - // assert_evals_to!("Num.sin 0", 0.0, f64); - // assert_evals_to!("Num.sin 1.41421356237", 0.9877659459922529, f64); - // } - - // #[test] - // fn cos() { - // assert_evals_to!("Num.cos 0", 1.0, f64); - // assert_evals_to!("Num.cos 3.14159265359", -1.0, f64); - // } - - // #[test] - // fn tan() { - // assert_evals_to!("Num.tan 0", 0.0, f64); - // assert_evals_to!("Num.tan 1", 1.557407724654902, f64); - // } - - // #[test] - // fn lt_i64() { - // assert_evals_to!("1 < 2", true, bool); - // assert_evals_to!("1 < 1", false, bool); - // assert_evals_to!("2 < 1", false, bool); - // assert_evals_to!("0 < 0", false, bool); - // } - - // #[test] - // fn lte_i64() { - // assert_evals_to!("1 <= 1", true, bool); - // assert_evals_to!("2 <= 1", false, bool); - // assert_evals_to!("1 <= 2", true, bool); - // assert_evals_to!("0 <= 0", true, bool); - // } - - // #[test] - // fn gt_i64() { - // assert_evals_to!("2 > 1", true, bool); - // assert_evals_to!("2 > 2", false, bool); - // assert_evals_to!("1 > 1", false, bool); - // assert_evals_to!("0 > 0", false, bool); - // } - - // #[test] - // fn gte_i64() { - // assert_evals_to!("1 >= 1", true, bool); - // assert_evals_to!("1 >= 2", false, bool); - // assert_evals_to!("2 >= 1", true, bool); - // assert_evals_to!("0 >= 0", true, bool); - // } - - // #[test] - // fn lt_f64() { - // assert_evals_to!("1.1 < 1.2", true, bool); - // assert_evals_to!("1.1 < 1.1", false, bool); - // assert_evals_to!("1.2 < 1.1", false, bool); - // assert_evals_to!("0.0 < 0.0", false, bool); - // } - - // #[test] - // fn lte_f64() { - // assert_evals_to!("1.1 <= 1.1", true, bool); - // assert_evals_to!("1.2 <= 1.1", false, bool); - // assert_evals_to!("1.1 <= 1.2", true, bool); - // assert_evals_to!("0.0 <= 0.0", true, bool); - // } - - // #[test] - // fn gt_f64() { - // assert_evals_to!("2.2 > 1.1", true, bool); - // assert_evals_to!("2.2 > 2.2", false, bool); - // assert_evals_to!("1.1 > 2.2", false, bool); - // assert_evals_to!("0.0 > 0.0", false, bool); - // } - - // #[test] - // fn gte_f64() { - // assert_evals_to!("1.1 >= 1.1", true, bool); - // assert_evals_to!("1.1 >= 1.2", false, bool); - // assert_evals_to!("1.2 >= 1.1", true, bool); - // assert_evals_to!("0.0 >= 0.0", true, bool); - // } - - #[test] - fn gen_order_of_arithmetic_ops() { - assert_evals_to!( - indoc!( - r#" - 1 + 3 * 7 - 2 - "# - ), - 20, - i64 - ); - } - - #[test] - fn gen_order_of_arithmetic_ops_complex_float() { - assert_evals_to!( - indoc!( - r#" - 3 - 48 * 2.0 - "# - ), - -93.0, - f64 - ); - } - - // #[test] - // fn if_guard_bind_variable_false() { - // assert_evals_to!( - // indoc!( - // r#" - // wrapper = \{} -> - // when 10 is - // x if x == 5 -> 0 - // _ -> 42 - - // wrapper {} - // "# - // ), - // 42, - // i64 - // ); - // } - - // #[test] - // fn if_guard_bind_variable_true() { - // assert_evals_to!( - // indoc!( - // r#" - // wrapper = \{} -> - // when 10 is - // x if x == 10 -> 42 - // _ -> 0 - - // wrapper {} - // "# - // ), - // 42, - // i64 - // ); - // } - - // #[test] - // fn tail_call_elimination() { - // assert_evals_to!( - // indoc!( - // r#" - // sum = \n, accum -> - // when n is - // 0 -> accum - // _ -> sum (n - 1) (n + accum) - - // sum 1_000_000 0 - // "# - // ), - // 500000500000, - // i64 - // ); - // } - - // #[test] - // fn int_negate() { - // assert_evals_to!("Num.neg 123", -123, i64); - // } - - // #[test] - // fn gen_wrap_int_neg() { - // assert_evals_to!( - // indoc!( - // r#" - // wrappedNeg = \num -> -num - - // wrappedNeg 3 - // "# - // ), - // -3, - // i64 - // ); - // } - - // #[test] - // fn int_to_float() { - // assert_evals_to!("Num.toFloat 0x9", 9.0, f64); - // } - - // #[test] - // fn num_to_float() { - // assert_evals_to!("Num.toFloat 9", 9.0, f64); - // } - - // #[test] - // fn float_to_float() { - // assert_evals_to!("Num.toFloat 0.5", 0.5, f64); - // } - - // #[test] - // fn int_compare() { - // assert_evals_to!("Num.compare 0 1", RocOrder::Lt, RocOrder); - // assert_evals_to!("Num.compare 1 1", RocOrder::Eq, RocOrder); - // assert_evals_to!("Num.compare 1 0", RocOrder::Gt, RocOrder); - // } - - // #[test] - // fn float_compare() { - // assert_evals_to!("Num.compare 0.01 3.14", RocOrder::Lt, RocOrder); - // assert_evals_to!("Num.compare 3.14 3.14", RocOrder::Eq, RocOrder); - // assert_evals_to!("Num.compare 3.14 0.01", RocOrder::Gt, RocOrder); - // } - - // #[test] - // fn pow() { - // assert_evals_to!("Num.pow 2.0 2.0", 4.0, f64); - // } - - // #[test] - // fn ceiling() { - // assert_evals_to!("Num.ceiling 1.1", 2, i64); - // } - - // #[test] - // fn floor() { - // assert_evals_to!("Num.floor 1.9", 1, i64); - // } - - // // #[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 - // // ); - // // } - - // #[test] - // fn max_i128() { - // assert_evals_to!( - // indoc!( - // r#" - // Num.maxI128 - // "# - // ), - // i128::MAX, - // i128 - // ); - // } - - // #[test] - // fn num_max_int() { - // assert_evals_to!( - // indoc!( - // r#" - // Num.maxInt - // "# - // ), - // i64::MAX, - // i64 - // ); - // } - - // #[test] - // fn num_min_int() { - // assert_evals_to!( - // indoc!( - // r#" - // Num.minInt - // "# - // ), - // i64::MIN, - // i64 - // ); - // } -} diff --git a/compiler/gen_wasm/tests/wasm_records.rs b/compiler/gen_wasm/tests/wasm_records.rs deleted file mode 100644 index 0e3d7d11c1..0000000000 --- a/compiler/gen_wasm/tests/wasm_records.rs +++ /dev/null @@ -1,916 +0,0 @@ -#[macro_use] -extern crate indoc; - -#[macro_use] -mod helpers; - -#[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))] -mod wasm_records { - // #[test] - // fn basic_record() { - // assert_evals_to!( - // indoc!( - // r#" - // { y: 17, x: 15, z: 19 }.x - // "# - // ), - // 15, - // i64 - // ); - // - // assert_evals_to!( - // indoc!( - // r#" - // { x: 15, y: 17, z: 19 }.y - // "# - // ), - // 17, - // i64 - // ); - // - // assert_evals_to!( - // indoc!( - // r#" - // { x: 15, y: 17, z: 19 }.z - // "# - // ), - // 19, - // i64 - // ); - // } - // - // #[test] - // fn nested_record() { - // assert_evals_to!( - // indoc!( - // r#" - // { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.x - // "# - // ), - // 15, - // i64 - // ); - // - // assert_evals_to!( - // indoc!( - // r#" - // { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.a - // "# - // ), - // 12, - // i64 - // ); - // - // assert_evals_to!( - // indoc!( - // r#" - // { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.b - // "# - // ), - // 15, - // i64 - // ); - // - // assert_evals_to!( - // indoc!( - // r#" - // { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.c - // "# - // ), - // 2, - // i64 - // ); - // - // assert_evals_to!( - // indoc!( - // r#" - // { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.z - // "# - // ), - // 19, - // i64 - // ); - // } - // - // #[test] - // fn f64_record() { - // assert_evals_to!( - // indoc!( - // r#" - // rec = { y: 17.2, x: 15.1, z: 19.3 } - // - // rec.x - // "# - // ), - // 15.1, - // f64 - // ); - // - // assert_evals_to!( - // indoc!( - // r#" - // rec = { y: 17.2, x: 15.1, z: 19.3 } - // - // rec.y - // "# - // ), - // 17.2, - // f64 - // ); - // - // assert_evals_to!( - // indoc!( - // r#" - // rec = { y: 17.2, x: 15.1, z: 19.3 } - // - // rec.z - // "# - // ), - // 19.3, - // f64 - // ); - // } - - // #[test] - // fn fn_record() { - // assert_evals_to!( - // indoc!( - // r#" - // getRec = \x -> { y: 17, x, z: 19 } - - // (getRec 15).x - // "# - // ), - // 15, - // i64 - // ); - - // assert_evals_to!( - // indoc!( - // r#" - // rec = { x: 15, y: 17, z: 19 } - - // rec.y - // "# - // ), - // 17, - // i64 - // ); - - // assert_evals_to!( - // indoc!( - // r#" - // rec = { x: 15, y: 17, z: 19 } - - // rec.z - // "# - // ), - // 19, - // i64 - // ); - - // assert_evals_to!( - // indoc!( - // r#" - // rec = { x: 15, y: 17, z: 19 } - - // rec.z + rec.x - // "# - // ), - // 34, - // i64 - // ); - // } - - // #[test] - // fn def_record() { - // assert_evals_to!( - // indoc!( - // r#" - // rec = { y: 17, x: 15, z: 19 } - // - // rec.x - // "# - // ), - // 15, - // i64 - // ); - // - // assert_evals_to!( - // indoc!( - // r#" - // rec = { x: 15, y: 17, z: 19 } - // - // rec.y - // "# - // ), - // 17, - // i64 - // ); - // - // assert_evals_to!( - // indoc!( - // r#" - // rec = { x: 15, y: 17, z: 19 } - // - // rec.z - // "# - // ), - // 19, - // i64 - // ); - // } - // - // #[test] - // fn when_on_record() { - // assert_evals_to!( - // indoc!( - // r#" - // when { x: 0x2 } is - // { x } -> x + 3 - // "# - // ), - // 5, - // i64 - // ); - // } - // - // #[test] - // fn when_record_with_guard_pattern() { - // assert_evals_to!( - // indoc!( - // r#" - // when { x: 0x2, y: 3.14 } is - // { x: var } -> var + 3 - // "# - // ), - // 5, - // i64 - // ); - // } - // - // #[test] - // fn let_with_record_pattern() { - // assert_evals_to!( - // indoc!( - // r#" - // { x } = { x: 0x2, y: 3.14 } - // - // x - // "# - // ), - // 2, - // i64 - // ); - // } - // - // #[test] - // fn record_guard_pattern() { - // assert_evals_to!( - // indoc!( - // r#" - // when { x: 0x2, y: 3.14 } is - // { x: 0x4 } -> 5 - // { x } -> x + 3 - // "# - // ), - // 5, - // i64 - // ); - // } - // - // #[test] - // fn twice_record_access() { - // assert_evals_to!( - // indoc!( - // r#" - // x = {a: 0x2, b: 0x3 } - // - // x.a + x.b - // "# - // ), - // 5, - // i64 - // ); - // } - // #[test] - // fn empty_record() { - // assert_evals_to!( - // indoc!( - // r#" - // v = {} - // - // v - // "# - // ), - // (), - // () - // ); - // } - - #[test] - fn i64_record1_literal() { - assert_evals_to!( - indoc!( - r#" - { x: 3 } - "# - ), - 3, - i64 - ); - } - - #[test] - fn i64_record2_literal() { - assert_evals_to!( - indoc!( - r#" - { x: 3, y: 5 } - "# - ), - (3, 5), - (i64, i64) - ); - } - - #[test] - fn i64_record3_literal() { - assert_evals_to!( - indoc!( - r#" - { x: 3, y: 5, z: 17 } - "# - ), - (3, 5, 17), - (i64, i64, i64) - ); - } - - #[test] - fn f64_record2_literal() { - assert_evals_to!( - indoc!( - r#" - { x: 3.1, y: 5.1 } - "# - ), - (3.1, 5.1), - (f64, f64) - ); - } - - #[test] - fn f64_record3_literal() { - assert_evals_to!( - indoc!( - r#" - { x: 3.1, y: 5.1, z: 17.1 } - "# - ), - (3.1, 5.1, 17.1), - (f64, f64, f64) - ); - } - - #[test] - fn bool_record4_literal() { - assert_evals_to!( - indoc!( - r#" - record : { a : Bool, b : Bool, c : Bool, d : Bool } - record = { a: True, b: False, c : False, d : True } - - record - "# - ), - [true, false, false, true], - [bool; 4] - ); - } - - #[test] - fn i64_record9_literal() { - assert_evals_to!( - indoc!( - r#" - { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 } - "# - ), - [3, 5, 17, 1, 9, 12, 13, 14, 15], - [i64; 9] - ); - } - - #[test] - fn bool_literal() { - assert_evals_to!( - indoc!( - r#" - x : Bool - x = True - - x - "# - ), - true, - bool - ); - } - - // #[test] - // fn optional_field_when_use_default() { - // assert_evals_to!( - // indoc!( - // r#" - // app "test" provides [ main ] to "./platform" - - // f = \r -> - // when r is - // { x: Blue, y ? 3 } -> y - // { x: Red, y ? 5 } -> y - - // main = - // a = f { x: Blue, y: 7 } - // b = f { x: Blue } - // c = f { x: Red, y: 11 } - // d = f { x: Red } - - // a * b * c * d - // "# - // ), - // 3 * 5 * 7 * 11, - // i64 - // ); - // } - - // #[test] - // fn optional_field_when_use_default_nested() { - // assert_evals_to!( - // indoc!( - // r#" - // f = \r -> - // when r is - // { x: Blue, y ? 3 } -> y - // { x: Red, y ? 5 } -> y - - // a = f { x: Blue, y: 7 } - // b = f { x: Blue } - // c = f { x: Red, y: 11 } - // d = f { x: Red } - - // a * b * c * d - // "# - // ), - // 3 * 5 * 7 * 11, - // i64 - // ); - // } - - // #[test] - // fn optional_field_when_no_use_default() { - // assert_evals_to!( - // indoc!( - // r#" - // app "test" provides [ main ] to "./platform" - - // f = \r -> - // { x ? 10, y } = r - // x + y - - // main = - // f { x: 4, y: 9 } - // "# - // ), - // 13, - // i64 - // ); - // } - - // #[test] - // fn optional_field_when_no_use_default_nested() { - // assert_evals_to!( - // indoc!( - // r#" - // f = \r -> - // { x ? 10, y } = r - // x + y - - // f { x: 4, y: 9 } - // "# - // ), - // 13, - // i64 - // ); - // } - - // #[test] - // fn optional_field_let_use_default() { - // assert_evals_to!( - // indoc!( - // r#" - // app "test" provides [ main ] to "./platform" - - // f = \r -> - // { x ? 10, y } = r - // x + y - - // main = - // f { y: 9 } - // "# - // ), - // 19, - // i64 - // ); - // } - - // #[test] - // fn optional_field_let_no_use_default() { - // assert_evals_to!( - // indoc!( - // r#" - // app "test" provides [ main ] to "./platform" - - // f = \r -> - // { x ? 10, y } = r - // x + y - - // main = - // f { x: 4, y: 9 } - // "# - // ), - // 13, - // i64 - // ); - // } - - // #[test] - // fn optional_field_let_no_use_default_nested() { - // assert_evals_to!( - // indoc!( - // r#" - // f = \r -> - // { x ? 10, y } = r - // x + y - - // f { x: 4, y: 9 } - // "# - // ), - // 13, - // i64 - // ); - // } - - // #[test] - // fn optional_field_function_use_default() { - // assert_evals_to!( - // indoc!( - // r#" - // f = \{ x ? 10, y } -> x + y - - // f { y: 9 } - // "# - // ), - // 19, - // i64 - // ); - // } - - // #[test] - // #[ignore] - // fn optional_field_function_no_use_default() { - // // blocked on https://github.com/rtfeldman/roc/issues/786 - // assert_evals_to!( - // indoc!( - // r#" - // app "test" provides [ main ] to "./platform" - - // f = \{ x ? 10, y } -> x + y - - // main = - // f { x: 4, y: 9 } - // "# - // ), - // 13, - // i64 - // ); - // } - - // #[test] - // #[ignore] - // fn optional_field_function_no_use_default_nested() { - // // blocked on https://github.com/rtfeldman/roc/issues/786 - // assert_evals_to!( - // indoc!( - // r#" - // f = \{ x ? 10, y } -> x + y - - // f { x: 4, y: 9 } - // "# - // ), - // 13, - // i64 - // ); - // } - - // #[test] - // fn optional_field_singleton_record() { - // assert_evals_to!( - // indoc!( - // r#" - // when { x : 4 } is - // { x ? 3 } -> x - // "# - // ), - // 4, - // i64 - // ); - // } - - // #[test] - // fn optional_field_empty_record() { - // assert_evals_to!( - // indoc!( - // r#" - // when { } is - // { x ? 3 } -> x - // "# - // ), - // 3, - // i64 - // ); - // } - - #[test] - fn return_record_3() { - assert_evals_to!( - indoc!( - r#" - { x: 3, y: 5, z: 4 } - "# - ), - (3, 5, 4), - (i64, i64, i64) - ); - } - - #[test] - fn return_record_4() { - assert_evals_to!( - indoc!( - r#" - { a: 3, b: 5, c: 4, d: 2 } - "# - ), - [3, 5, 4, 2], - [i64; 4] - ); - } - - #[test] - fn return_record_5() { - assert_evals_to!( - indoc!( - r#" - { a: 3, b: 5, c: 4, d: 2, e: 1 } - "# - ), - [3, 5, 4, 2, 1], - [i64; 5] - ); - } - - #[test] - fn return_record_6() { - assert_evals_to!( - indoc!( - r#" - { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 } - "# - ), - [3, 5, 4, 2, 1, 7], - [i64; 6] - ); - } - - #[test] - fn return_record_7() { - assert_evals_to!( - indoc!( - r#" - { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 } - "# - ), - [3, 5, 4, 2, 1, 7, 8], - [i64; 7] - ); - } - - #[test] - fn return_record_float_int() { - assert_evals_to!( - indoc!( - r#" - { a: 3.14, b: 0x1 } - "# - ), - (3.14, 0x1), - (f64, i64) - ); - } - - #[test] - fn return_record_int_float() { - assert_evals_to!( - indoc!( - r#" - { a: 0x1, b: 3.14 } - "# - ), - (0x1, 3.14), - (i64, f64) - ); - } - - #[test] - fn return_record_float_float() { - assert_evals_to!( - indoc!( - r#" - { a: 6.28, b: 3.14 } - "# - ), - (6.28, 3.14), - (f64, f64) - ); - } - - #[test] - fn return_record_float_float_float() { - assert_evals_to!( - indoc!( - r#" - { a: 6.28, b: 3.14, c: 0.1 } - "# - ), - (6.28, 3.14, 0.1), - (f64, f64, f64) - ); - } - - // #[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 accessor() { - // assert_evals_to!( - // indoc!( - // r#" - // .foo { foo: 4 } + .foo { bar: 6.28, foo: 3 } - // "# - // ), - // 7, - // i64 - // ); - // } - - // #[test] - // fn accessor_single_element_record() { - // assert_evals_to!( - // indoc!( - // r#" - // .foo { foo: 4 } - // "# - // ), - // 4, - // i64 - // ); - // } - - // #[test] - // fn update_record() { - // assert_evals_to!( - // indoc!( - // r#" - // rec = { foo: 42, bar: 6 } - - // { rec & foo: rec.foo + 1 } - // "# - // ), - // (6, 43), - // (i64, i64) - // ); - // } - - // #[test] - // fn update_single_element_record() { - // assert_evals_to!( - // indoc!( - // r#" - // rec = { foo: 42} - - // { rec & foo: rec.foo + 1 } - // "# - // ), - // 43, - // i64 - // ); - // } - - // #[test] - // fn booleans_in_record() { - // assert_evals_to!( - // indoc!("{ x: 1 == 1, y: 1 == 1 }"), - // (true, true), - // (bool, bool) - // ); - // assert_evals_to!( - // indoc!("{ x: 1 != 1, y: 1 == 1 }"), - // (false, true), - // (bool, bool) - // ); - // assert_evals_to!( - // indoc!("{ x: 1 == 1, y: 1 != 1 }"), - // (true, false), - // (bool, bool) - // ); - // assert_evals_to!( - // indoc!("{ x: 1 != 1, y: 1 != 1 }"), - // (false, false), - // (bool, bool) - // ); - // } - - // #[test] - // fn alignment_in_record() { - // assert_evals_to!( - // indoc!("{ c: 32, b: if True then Red else if True then Green else Blue, a: 1 == 1 }"), - // (32i64, true, 2u8), - // (i64, bool, u8) - // ); - // } - - #[test] - fn stack_memory_return_from_branch() { - // stack memory pointer should end up in the right place after returning from a branch - assert_evals_to!( - indoc!( - r#" - stackMemoryJunk = { x: 999, y: 111 } - if True then - { x: 123, y: 321 } - else - stackMemoryJunk - "# - ), - (123, 321), - (i64, i64) - ); - } - - // #[test] - // fn blue_and_present() { - // assert_evals_to!( - // indoc!( - // r#" - // f = \r -> - // when r is - // { x: Blue, y ? 3 } -> y - // { x: Red, y ? 5 } -> y - - // f { x: Blue, y: 7 } - // "# - // ), - // 7, - // i64 - // ); - // } - - // #[test] - // fn blue_and_absent() { - // assert_evals_to!( - // indoc!( - // r#" - // f = \r -> - // when r is - // { x: Blue, y ? 3 } -> y - // { x: Red, y ? 5 } -> y - - // f { x: Blue } - // "# - // ), - // 3, - // i64 - // ); - // } -} diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 6f54523532..60e8d7a5f2 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -1004,7 +1004,7 @@ impl ModuleTiming { .checked_sub(*read_roc_file) }; - calculate(end_time.duration_since(*start_time)).unwrap_or_else(Duration::default) + calculate(end_time.duration_since(*start_time)).unwrap_or_default() } } diff --git a/compiler/module/Cargo.toml b/compiler/module/Cargo.toml index 57e169bee1..06602feab1 100644 --- a/compiler/module/Cargo.toml +++ b/compiler/module/Cargo.toml @@ -16,5 +16,4 @@ snafu = { version = "0.6", features = ["backtraces"] } [dev-dependencies] pretty_assertions = "0.5.1" -maplit = "1.0.1" indoc = "0.3.3" diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index c38acfc71e..58eea72a3b 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -33,6 +33,7 @@ pub enum LowLevel { ListMap, ListMap2, ListMap3, + ListMap4, ListMapWithIndex, ListKeepIf, ListWalk, @@ -211,6 +212,7 @@ macro_rules! higher_order { ListMap | ListMap2 | ListMap3 + | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk @@ -243,6 +245,7 @@ impl LowLevel { ListMap => 1, ListMap2 => 2, ListMap3 => 3, + ListMap4 => 4, ListMapWithIndex => 1, ListKeepIf => 1, ListWalk => 2, diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 56acdddcb2..79510c3700 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -1060,6 +1060,7 @@ define_builtins! { 37 LIST_MIN_LT: "#minlt" 38 LIST_MAX: "max" 39 LIST_MAX_GT: "#maxGt" + 40 LIST_MAP4: "map4" } 5 RESULT: "Result" => { 0 RESULT_RESULT: "Result" imported // the Result.Result type alias diff --git a/compiler/mono/Cargo.toml b/compiler/mono/Cargo.toml index c227760d86..6d0faa5ec9 100644 --- a/compiler/mono/Cargo.toml +++ b/compiler/mono/Cargo.toml @@ -29,7 +29,6 @@ roc_builtins = { path = "../builtins" } roc_parse = { path = "../parse" } roc_solve = { path = "../solve" } pretty_assertions = "0.5.1" -maplit = "1.0.1" indoc = "0.3.3" quickcheck = "0.8" quickcheck_macros = "0.8" diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index f029388086..2a0d4b1c18 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -561,6 +561,84 @@ fn build_tuple_type(builder: &mut impl TypeContext, layouts: &[Layout]) -> Resul builder.add_tuple_type(&field_types) } +#[repr(u32)] +#[derive(Clone, Copy)] +enum KeepResult { + Errs = ERR_TAG_ID, + Oks = OK_TAG_ID, +} + +impl KeepResult { + fn invert(&self) -> Self { + match self { + KeepResult::Errs => KeepResult::Oks, + KeepResult::Oks => KeepResult::Errs, + } + } +} + +#[derive(Clone, Copy)] +enum ResultRepr<'a> { + Int1, + NonRecursive { err: Layout<'a>, ok: Layout<'a> }, +} + +impl<'a> ResultRepr<'a> { + fn from_layout(layout: &Layout<'a>) -> Self { + match layout { + Layout::Union(UnionLayout::NonRecursive(tags)) => ResultRepr::NonRecursive { + err: tags[ERR_TAG_ID as usize][0], + ok: tags[OK_TAG_ID as usize][0], + }, + Layout::Builtin(Builtin::Int1) => ResultRepr::Int1, + other => unreachable!("unexpected layout: {:?}", other), + } + } + + fn unwrap( + &self, + builder: &mut FuncDefBuilder, + block: BlockId, + err_or_ok: ValueId, + keep_tag_id: u32, + ) -> Result { + match self { + ResultRepr::NonRecursive { .. } => { + let unwrapped = builder.add_unwrap_union(block, err_or_ok, keep_tag_id)?; + + builder.add_get_tuple_field(block, unwrapped, 0) + } + ResultRepr::Int1 => builder.add_make_tuple(block, &[]), + } + } +} + +fn add_loop( + builder: &mut FuncDefBuilder, + block: BlockId, + state_type: TypeId, + init_state: ValueId, + make_body: impl for<'a> FnOnce(&'a mut FuncDefBuilder, BlockId, ValueId) -> Result, +) -> Result { + let sub_block = builder.add_block(); + let (loop_cont, loop_arg) = builder.declare_continuation(sub_block, state_type, state_type)?; + let body = builder.add_block(); + let ret_branch = builder.add_block(); + let loop_branch = builder.add_block(); + let new_state = make_body(builder, loop_branch, loop_arg)?; + let unreachable = builder.add_jump(loop_branch, loop_cont, new_state, state_type)?; + let result = builder.add_choice( + body, + &[ + BlockExpr(ret_branch, loop_arg), + BlockExpr(loop_branch, unreachable), + ], + )?; + builder.define_continuation(loop_cont, BlockExpr(body, result))?; + let unreachable = builder.add_jump(sub_block, loop_cont, init_state, state_type)?; + builder.add_sub_block(block, BlockExpr(sub_block, unreachable)) +} + fn call_spec( builder: &mut FuncDefBuilder, env: &Env, @@ -613,6 +691,7 @@ fn call_spec( HigherOrderLowLevel { specialization_id, closure_env_layout, + update_mode, op, arg_layouts, ret_layout, @@ -620,194 +699,405 @@ fn call_spec( function_env, .. } => { + use crate::low_level::HigherOrder::*; + let array = specialization_id.to_bytes(); let spec_var = CalleeSpecVar(&array); + let mode = update_mode.to_bytes(); + let update_mode_var = UpdateModeVar(&mode); + let it = arg_layouts.iter().copied(); let bytes = func_name_bytes_help(*function_name, it, *ret_layout); let name = FuncName(&bytes); let module = MOD_APP; - use crate::low_level::HigherOrder::*; + let closure_env = env.symbols[function_env]; + + macro_rules! call_function { + ($builder: expr, $block:expr, [$($arg:expr),+ $(,)?]) => {{ + let argument = if closure_env_layout.is_none() { + $builder.add_make_tuple($block, &[$($arg),+])? + } else { + $builder.add_make_tuple($block, &[$($arg),+, closure_env])? + }; + + $builder.add_call($block, spec_var, module, name, argument)? + }}; + } match op { DictWalk { xs, state } => { let dict = env.symbols[xs]; let state = env.symbols[state]; - let closure_env = env.symbols[function_env]; - let bag = builder.add_get_tuple_field(block, dict, DICT_BAG_INDEX)?; - let _cell = builder.add_get_tuple_field(block, dict, DICT_CELL_INDEX)?; + let loop_body = |builder: &mut FuncDefBuilder, block, state| { + let bag = builder.add_get_tuple_field(block, dict, DICT_BAG_INDEX)?; - let first = builder.add_bag_get(block, bag)?; + let element = builder.add_bag_get(block, bag)?; - let key = builder.add_get_tuple_field(block, first, 0)?; - let val = builder.add_get_tuple_field(block, first, 1)?; + let key = builder.add_get_tuple_field(block, element, 0)?; + let val = builder.add_get_tuple_field(block, element, 1)?; - let argument = if closure_env_layout.is_none() { - builder.add_make_tuple(block, &[state, key, val])? - } else { - builder.add_make_tuple(block, &[state, key, val, closure_env])? + let new_state = call_function!(builder, block, [state, key, val]); + + Ok(new_state) }; - builder.add_call(block, spec_var, module, name, argument)?; + + let state_layout = arg_layouts[0]; + let state_type = layout_spec(builder, &state_layout)?; + let init_state = state; + + add_loop(builder, block, state_type, init_state, loop_body) } - ListWalk { xs, state } - | ListWalkBackwards { xs, state } - | ListWalkUntil { xs, state } => { + ListWalk { xs, state } | ListWalkBackwards { xs, state } => { let list = env.symbols[xs]; let state = env.symbols[state]; - let closure_env = env.symbols[function_env]; - let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; - let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + let loop_body = |builder: &mut FuncDefBuilder, block, state| { + let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; - let first = builder.add_bag_get(block, bag)?; + let element = builder.add_bag_get(block, bag)?; - let argument = if closure_env_layout.is_none() { - builder.add_make_tuple(block, &[state, first])? - } else { - builder.add_make_tuple(block, &[state, first, closure_env])? + let new_state = call_function!(builder, block, [state, element]); + + Ok(new_state) }; - builder.add_call(block, spec_var, module, name, argument)?; + + let state_layout = arg_layouts[0]; + let state_type = layout_spec(builder, &state_layout)?; + let init_state = state; + + add_loop(builder, block, state_type, init_state, loop_body) + } + ListWalkUntil { xs, state } => { + let list = env.symbols[xs]; + let state = env.symbols[state]; + + let loop_body = |builder: &mut FuncDefBuilder, block, state| { + let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; + + let element = builder.add_bag_get(block, bag)?; + + let continue_or_stop = call_function!(builder, block, [state, element]); + + // just assume it is a continue + let unwrapped = builder.add_unwrap_union(block, continue_or_stop, 0)?; + let new_state = builder.add_get_tuple_field(block, unwrapped, 0)?; + + Ok(new_state) + }; + + let state_layout = arg_layouts[0]; + let state_type = layout_spec(builder, &state_layout)?; + let init_state = state; + + add_loop(builder, block, state_type, init_state, loop_body) } ListMapWithIndex { xs } => { let list = env.symbols[xs]; - let closure_env = env.symbols[function_env]; - let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; - let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + let loop_body = |builder: &mut FuncDefBuilder, block, state| { + let input_bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; - let first = builder.add_bag_get(block, bag)?; - let index = builder.add_make_tuple(block, &[])?; + let element = builder.add_bag_get(block, input_bag)?; + let index = builder.add_make_tuple(block, &[])?; - let argument = if closure_env_layout.is_none() { - builder.add_make_tuple(block, &[index, first])? - } else { - builder.add_make_tuple(block, &[index, first, closure_env])? + let new_element = call_function!(builder, block, [index, element]); + + list_append(builder, block, update_mode_var, state, new_element) }; - builder.add_call(block, spec_var, module, name, argument)?; + + let output_element_type = layout_spec(builder, ret_layout)?; + + let state_layout = Layout::Builtin(Builtin::List(ret_layout)); + let state_type = layout_spec(builder, &state_layout)?; + + let init_state = new_list(builder, block, output_element_type)?; + + add_loop(builder, block, state_type, init_state, loop_body) } ListMap { xs } => { let list = env.symbols[xs]; - let closure_env = env.symbols[function_env]; - let bag1 = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; - let _cell1 = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + let loop_body = |builder: &mut FuncDefBuilder, block, state| { + let input_bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; - let elem1 = builder.add_bag_get(block, bag1)?; + let element = builder.add_bag_get(block, input_bag)?; - let argument = if closure_env_layout.is_none() { - builder.add_make_tuple(block, &[elem1])? - } else { - builder.add_make_tuple(block, &[elem1, closure_env])? + let new_element = call_function!(builder, block, [element]); + + list_append(builder, block, update_mode_var, state, new_element) }; - builder.add_call(block, spec_var, module, name, argument)?; + + let output_element_type = layout_spec(builder, ret_layout)?; + + let state_layout = Layout::Builtin(Builtin::List(ret_layout)); + let state_type = layout_spec(builder, &state_layout)?; + + let init_state = new_list(builder, block, output_element_type)?; + + add_loop(builder, block, state_type, init_state, loop_body) } ListSortWith { xs } => { let list = env.symbols[xs]; - let closure_env = env.symbols[function_env]; - let bag1 = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; - let _cell1 = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + let loop_body = |builder: &mut FuncDefBuilder, block, state| { + let bag = builder.add_get_tuple_field(block, state, LIST_BAG_INDEX)?; + let cell = builder.add_get_tuple_field(block, state, LIST_CELL_INDEX)?; - let elem1 = builder.add_bag_get(block, bag1)?; + let element_1 = builder.add_bag_get(block, bag)?; + let element_2 = builder.add_bag_get(block, bag)?; - let argument = if closure_env_layout.is_none() { - builder.add_make_tuple(block, &[elem1, elem1])? - } else { - builder.add_make_tuple(block, &[elem1, elem1, closure_env])? + let _ = call_function!(builder, block, [element_1, element_2]); + + builder.add_update(block, update_mode_var, cell)?; + + let new_cell = builder.add_new_heap_cell(block)?; + builder.add_make_tuple(block, &[new_cell, bag]) }; - builder.add_call(block, spec_var, module, name, argument)?; + + let state_layout = Layout::Builtin(Builtin::List(&arg_layouts[0])); + let state_type = layout_spec(builder, &state_layout)?; + let init_state = list; + + add_loop(builder, block, state_type, init_state, loop_body) } ListMap2 { xs, ys } => { let list1 = env.symbols[xs]; let list2 = env.symbols[ys]; - let closure_env = env.symbols[function_env]; - let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?; - let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?; - let elem1 = builder.add_bag_get(block, bag1)?; + let loop_body = |builder: &mut FuncDefBuilder, block, state| { + let input_bag_1 = + builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?; + let input_bag_2 = + builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?; - let bag2 = builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?; - let _cell2 = builder.add_get_tuple_field(block, list2, LIST_CELL_INDEX)?; - let elem2 = builder.add_bag_get(block, bag2)?; + let element_1 = builder.add_bag_get(block, input_bag_1)?; + let element_2 = builder.add_bag_get(block, input_bag_2)?; - let argument = if closure_env_layout.is_none() { - builder.add_make_tuple(block, &[elem1, elem2])? - } else { - builder.add_make_tuple(block, &[elem1, elem2, closure_env])? + let new_element = call_function!(builder, block, [element_1, element_2]); + + list_append(builder, block, update_mode_var, state, new_element) }; - builder.add_call(block, spec_var, module, name, argument)?; + + let output_element_type = layout_spec(builder, ret_layout)?; + + let state_layout = Layout::Builtin(Builtin::List(ret_layout)); + let state_type = layout_spec(builder, &state_layout)?; + + let init_state = new_list(builder, block, output_element_type)?; + + add_loop(builder, block, state_type, init_state, loop_body) } ListMap3 { xs, ys, zs } => { let list1 = env.symbols[xs]; let list2 = env.symbols[ys]; let list3 = env.symbols[zs]; - let closure_env = env.symbols[function_env]; - let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?; - let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?; - let elem1 = builder.add_bag_get(block, bag1)?; + let loop_body = |builder: &mut FuncDefBuilder, block, state| { + let input_bag_1 = + builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?; + let input_bag_2 = + builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?; + let input_bag_3 = + builder.add_get_tuple_field(block, list3, LIST_BAG_INDEX)?; - let bag2 = builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?; - let _cell2 = builder.add_get_tuple_field(block, list2, LIST_CELL_INDEX)?; - let elem2 = builder.add_bag_get(block, bag2)?; + let element_1 = builder.add_bag_get(block, input_bag_1)?; + let element_2 = builder.add_bag_get(block, input_bag_2)?; + let element_3 = builder.add_bag_get(block, input_bag_3)?; - let bag3 = builder.add_get_tuple_field(block, list3, LIST_BAG_INDEX)?; - let _cell3 = builder.add_get_tuple_field(block, list3, LIST_CELL_INDEX)?; - let elem3 = builder.add_bag_get(block, bag3)?; + let new_element = + call_function!(builder, block, [element_1, element_2, element_3]); - let argument = if closure_env_layout.is_none() { - builder.add_make_tuple(block, &[elem1, elem2, elem3])? - } else { - builder.add_make_tuple(block, &[elem1, elem2, elem3, closure_env])? + list_append(builder, block, update_mode_var, state, new_element) }; - builder.add_call(block, spec_var, module, name, argument)?; + + let output_element_type = layout_spec(builder, ret_layout)?; + + let state_layout = Layout::Builtin(Builtin::List(ret_layout)); + let state_type = layout_spec(builder, &state_layout)?; + + let init_state = new_list(builder, block, output_element_type)?; + + add_loop(builder, block, state_type, init_state, loop_body) + } + ListMap4 { xs, ys, zs, ws } => { + let list1 = env.symbols[xs]; + let list2 = env.symbols[ys]; + let list3 = env.symbols[zs]; + let list4 = env.symbols[ws]; + + let loop_body = |builder: &mut FuncDefBuilder, block, state| { + let input_bag_1 = + builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?; + let input_bag_2 = + builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?; + let input_bag_3 = + builder.add_get_tuple_field(block, list3, LIST_BAG_INDEX)?; + let input_bag_4 = + builder.add_get_tuple_field(block, list4, LIST_BAG_INDEX)?; + + let element_1 = builder.add_bag_get(block, input_bag_1)?; + let element_2 = builder.add_bag_get(block, input_bag_2)?; + let element_3 = builder.add_bag_get(block, input_bag_3)?; + let element_4 = builder.add_bag_get(block, input_bag_4)?; + + let new_element = call_function!( + builder, + block, + [element_1, element_2, element_3, element_4] + ); + + list_append(builder, block, update_mode_var, state, new_element) + }; + + let output_element_type = layout_spec(builder, ret_layout)?; + + let state_layout = Layout::Builtin(Builtin::List(ret_layout)); + let state_type = layout_spec(builder, &state_layout)?; + + let init_state = new_list(builder, block, output_element_type)?; + + add_loop(builder, block, state_type, init_state, loop_body) } - ListKeepIf { xs } | ListKeepOks { xs } | ListKeepErrs { xs } => { + ListKeepIf { xs } => { let list = env.symbols[xs]; - let closure_env = env.symbols[function_env]; - let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; - // let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + let loop_body = |builder: &mut FuncDefBuilder, block, state| { + let bag = builder.add_get_tuple_field(block, state, LIST_BAG_INDEX)?; + let cell = builder.add_get_tuple_field(block, state, LIST_CELL_INDEX)?; - let first = builder.add_bag_get(block, bag)?; + let element = builder.add_bag_get(block, bag)?; - let argument = if closure_env_layout.is_none() { - builder.add_make_tuple(block, &[first])? - } else { - builder.add_make_tuple(block, &[first, closure_env])? + let _ = call_function!(builder, block, [element]); + + // NOTE: we assume the element is not kept + builder.add_update(block, update_mode_var, cell)?; + + let removed = builder.add_bag_remove(block, bag)?; + + // decrement the removed element + let removed_element = builder.add_get_tuple_field(block, removed, 1)?; + builder.add_recursive_touch(block, removed_element)?; + + let new_bag = builder.add_get_tuple_field(block, removed, 0)?; + let new_cell = builder.add_new_heap_cell(block)?; + + builder.add_make_tuple(block, &[new_cell, new_bag]) }; - let result = builder.add_call(block, spec_var, module, name, argument)?; - let unit = builder.add_tuple_type(&[])?; - builder.add_unknown_with(block, &[result], unit)?; + + let state_layout = Layout::Builtin(Builtin::List(&arg_layouts[0])); + let state_type = layout_spec(builder, &state_layout)?; + let init_state = list; + + add_loop(builder, block, state_type, init_state, loop_body) + } + ListKeepOks { xs } | ListKeepErrs { xs } => { + let list = env.symbols[xs]; + + let keep_result = match op { + ListKeepOks { .. } => KeepResult::Oks, + ListKeepErrs { .. } => KeepResult::Errs, + _ => unreachable!(), + }; + + let result_repr = ResultRepr::from_layout(ret_layout); + + let output_element_layout = match (keep_result, result_repr) { + (KeepResult::Errs, ResultRepr::NonRecursive { err, .. }) => err, + (KeepResult::Oks, ResultRepr::NonRecursive { ok, .. }) => ok, + (_, ResultRepr::Int1) => Layout::Struct(&[]), + }; + + let loop_body = |builder: &mut FuncDefBuilder, block, state| { + let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; + + let element = builder.add_bag_get(block, bag)?; + + let err_or_ok = call_function!(builder, block, [element]); + + let kept_branch = builder.add_block(); + let not_kept_branch = builder.add_block(); + + let element_kept = { + let block = kept_branch; + + // a Result can be represented as a Int1 + let new_element = result_repr.unwrap( + builder, + block, + err_or_ok, + keep_result as u32, + )?; + + list_append(builder, block, update_mode_var, state, new_element)? + }; + + let element_not_kept = { + let block = not_kept_branch; + + // a Result can be represented as a Int1 + let dropped_element = result_repr.unwrap( + builder, + block, + err_or_ok, + keep_result.invert() as u32, + )?; + + // decrement the element we will not keep + builder.add_recursive_touch(block, dropped_element)?; + + state + }; + + builder.add_choice( + block, + &[ + BlockExpr(not_kept_branch, element_not_kept), + BlockExpr(kept_branch, element_kept), + ], + ) + }; + + let output_element_type = layout_spec(builder, &output_element_layout)?; + let init_state = new_list(builder, block, output_element_type)?; + + let state_layout = Layout::Builtin(Builtin::List(&output_element_layout)); + let state_type = layout_spec(builder, &state_layout)?; + + add_loop(builder, block, state_type, init_state, loop_body) } } - - // TODO overly pessimstic - // filter_map because one of the arguments is a function name, which - // is not defined in the env - let arguments: Vec<_> = call - .arguments - .iter() - .filter_map(|symbol| env.symbols.get(symbol)) - .copied() - .collect(); - - let result_type = layout_spec(builder, layout)?; - - builder.add_unknown_with(block, &arguments, result_type) } } } +fn list_append( + builder: &mut FuncDefBuilder, + block: BlockId, + update_mode_var: UpdateModeVar, + list: ValueId, + to_insert: ValueId, +) -> Result { + let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; + let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + + let _unit = builder.add_update(block, update_mode_var, cell)?; + + let new_bag = builder.add_bag_insert(block, bag, to_insert)?; + + let new_cell = builder.add_new_heap_cell(block)?; + builder.add_make_tuple(block, &[new_cell, new_bag]) +} + fn lowlevel_spec( builder: &mut FuncDefBuilder, env: &Env, @@ -883,6 +1173,10 @@ fn lowlevel_spec( let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + // decrement the overwritten element + let overwritten = builder.add_bag_get(block, bag)?; + let _unit = builder.add_recursive_touch(block, overwritten)?; + let _unit = builder.add_update(block, update_mode_var, cell)?; builder.add_bag_insert(block, bag, to_insert)?; @@ -916,16 +1210,7 @@ fn lowlevel_spec( let list = env.symbols[&arguments[0]]; let to_insert = env.symbols[&arguments[1]]; - let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; - let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; - - let _unit = builder.add_update(block, update_mode_var, cell)?; - - // TODO new heap cell - builder.add_bag_insert(block, bag, to_insert)?; - - let new_cell = builder.add_new_heap_cell(block)?; - builder.add_make_tuple(block, &[new_cell, bag]) + list_append(builder, block, update_mode_var, list, to_insert) } StrToUtf8 => { let string = env.symbols[&arguments[0]]; @@ -1425,8 +1710,8 @@ fn static_list_type(builder: &mut TC) -> Result { builder.add_tuple_type(&[cell, bag]) } -// const OK_TAG_ID: u8 = 1u8; -// const ERR_TAG_ID: u8 = 0u8; +const OK_TAG_ID: u32 = 1; +const ERR_TAG_ID: u32 = 0; const LIST_CELL_INDEX: u32 = 0; const LIST_BAG_INDEX: u32 = 1; diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 2c2430d0fb..b35f33610b 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -651,6 +651,21 @@ impl<'a> BorrowInfState<'a> { self.own_var(*zs); } } + ListMap4 { xs, ys, zs, ws } => { + // own the lists if the function wants to own the element + if !function_ps[0].borrow { + self.own_var(*xs); + } + if !function_ps[1].borrow { + self.own_var(*ys); + } + if !function_ps[2].borrow { + self.own_var(*zs); + } + if !function_ps[3].borrow { + self.own_var(*ws); + } + } ListSortWith { xs } => { // always own the input list self.own_var(*xs); @@ -933,6 +948,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { ListMap | ListMapWithIndex => arena.alloc_slice_copy(&[owned, function, closure_data]), ListMap2 => arena.alloc_slice_copy(&[owned, owned, function, closure_data]), ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, function, closure_data]), + ListMap4 => arena.alloc_slice_copy(&[owned, owned, owned, owned, function, closure_data]), ListKeepIf | ListKeepOks | ListKeepErrs => { arena.alloc_slice_copy(&[owned, function, closure_data]) } diff --git a/compiler/mono/src/inc_dec.rs b/compiler/mono/src/inc_dec.rs index d7363a9195..f023ecdaae 100644 --- a/compiler/mono/src/inc_dec.rs +++ b/compiler/mono/src/inc_dec.rs @@ -467,6 +467,7 @@ impl<'a> Context<'a> { op, closure_env_layout, specialization_id, + update_mode, arg_layouts, ret_layout, function_name, @@ -485,6 +486,7 @@ impl<'a> Context<'a> { closure_env_layout: *closure_env_layout, function_owns_closure_data: true, specialization_id: *specialization_id, + update_mode: *update_mode, function_name: *function_name, function_env: *function_env, arg_layouts, @@ -576,6 +578,27 @@ impl<'a> Context<'a> { &*self.arena.alloc(Stmt::Let(z, v, l, b)) } + ListMap4 { xs, ys, zs, ws } => { + let borrows = [ + function_ps[0].borrow, + function_ps[1].borrow, + function_ps[2].borrow, + function_ps[3].borrow, + FUNCTION, + CLOSURE_DATA, + ]; + + let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars); + + let b = decref_if_owned!(function_ps[0].borrow, *xs, b); + let b = decref_if_owned!(function_ps[1].borrow, *ys, b); + let b = decref_if_owned!(function_ps[2].borrow, *zs, b); + let b = decref_if_owned!(function_ps[3].borrow, *ws, b); + + let v = create_call!(function_ps.get(3)); + + &*self.arena.alloc(Stmt::Let(z, v, l, b)) + } ListMapWithIndex { xs } => { let borrows = [function_ps[1].borrow, FUNCTION, CLOSURE_DATA]; diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 780d08dd58..6ef12f8814 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -1181,6 +1181,10 @@ pub enum CallType<'a> { /// specialization id of the function argument, used for name generation specialization_id: CallSpecId, + + /// update mode of the higher order lowlevel itself + update_mode: UpdateModeId, + /// function layout, used for name generation arg_layouts: &'a [Layout<'a>], ret_layout: Layout<'a>, @@ -4016,11 +4020,12 @@ pub fn with_hole<'a>( lambda_set, op, closure_data_symbol, - |top_level_function, closure_data, closure_env_layout, specialization_id| self::Call { + |(top_level_function, closure_data, closure_env_layout, specialization_id, update_mode)| self::Call { call_type: CallType::HigherOrderLowLevel { op: crate::low_level::HigherOrder::$ho { $($x,)* }, closure_env_layout, specialization_id, + update_mode, function_owns_closure_data: false, function_env: closure_data_symbol, function_name: top_level_function, @@ -4144,6 +4149,16 @@ pub fn with_hole<'a>( match_on_closure_argument!(ListMap3, [xs, ys, zs]) } + ListMap4 => { + debug_assert_eq!(arg_symbols.len(), 5); + + let xs = arg_symbols[0]; + let ys = arg_symbols[1]; + let zs = arg_symbols[2]; + let ws = arg_symbols[3]; + + match_on_closure_argument!(ListMap4, [xs, ys, zs, ws]) + } _ => { let call = self::Call { call_type: CallType::LowLevel { @@ -6192,7 +6207,7 @@ fn reuse_function_symbol<'a>( // and closures by unification. Here we record whether this function captures // anything. let captures = partial_proc.captured_symbols.captures(); - let captured = partial_proc.captured_symbols.clone(); + let captured = partial_proc.captured_symbols; match res_layout { RawFunctionLayout::Function(_, lambda_set, _) => { @@ -7903,6 +7918,8 @@ pub fn num_argument_to_int_or_float( } } +type ToLowLevelCallArguments<'a> = (Symbol, Symbol, Option>, CallSpecId, UpdateModeId); + /// Use the lambda set to figure out how to make a lowlevel call #[allow(clippy::too_many_arguments)] fn lowlevel_match_on_lambda_set<'a, ToLowLevelCall>( @@ -7916,7 +7933,7 @@ fn lowlevel_match_on_lambda_set<'a, ToLowLevelCall>( hole: &'a Stmt<'a>, ) -> Stmt<'a> where - ToLowLevelCall: Fn(Symbol, Symbol, Option>, CallSpecId) -> Call<'a> + Copy, + ToLowLevelCall: Fn(ToLowLevelCallArguments<'a>) -> Call<'a> + Copy, { match lambda_set.runtime_representation() { Layout::Union(union_layout) => { @@ -7951,12 +7968,14 @@ where Layout::Struct(_) => match lambda_set.set.get(0) { Some((function_symbol, _)) => { let call_spec_id = env.next_call_specialization_id(); - let call = to_lowlevel_call( + let update_mode = env.next_update_mode_id(); + let call = to_lowlevel_call(( *function_symbol, closure_data_symbol, lambda_set.is_represented(), call_spec_id, - ); + update_mode, + )); build_call(env, call, assigned, return_layout, env.arena.alloc(hole)) } @@ -8021,7 +8040,7 @@ fn lowlevel_union_lambda_set_to_switch<'a, ToLowLevelCall>( hole: &'a Stmt<'a>, ) -> Stmt<'a> where - ToLowLevelCall: Fn(Symbol, Symbol, Option>, CallSpecId) -> Call<'a> + Copy, + ToLowLevelCall: Fn(ToLowLevelCallArguments<'a>) -> Call<'a> + Copy, { debug_assert!(!lambda_set.is_empty()); @@ -8035,12 +8054,14 @@ where let hole = Stmt::Jump(join_point_id, env.arena.alloc([assigned])); let call_spec_id = env.next_call_specialization_id(); - let call = to_lowlevel_call( + let update_mode = env.next_update_mode_id(); + let call = to_lowlevel_call(( *function_symbol, closure_data_symbol, closure_env_layout, call_spec_id, - ); + update_mode, + )); let stmt = build_call(env, call, assigned, return_layout, env.arena.alloc(hole)); branches.push((i as u64, BranchInfo::None, stmt)); @@ -8458,7 +8479,7 @@ fn lowlevel_enum_lambda_set_to_switch<'a, ToLowLevelCall>( hole: &'a Stmt<'a>, ) -> Stmt<'a> where - ToLowLevelCall: Fn(Symbol, Symbol, Option>, CallSpecId) -> Call<'a> + Copy, + ToLowLevelCall: Fn(ToLowLevelCallArguments<'a>) -> Call<'a> + Copy, { debug_assert!(!lambda_set.is_empty()); @@ -8472,12 +8493,14 @@ where let hole = Stmt::Jump(join_point_id, env.arena.alloc([result_symbol])); let call_spec_id = env.next_call_specialization_id(); - let call = to_lowlevel_call( + let update_mode = env.next_update_mode_id(); + let call = to_lowlevel_call(( *function_symbol, closure_data_symbol, closure_env_layout, call_spec_id, - ); + update_mode, + )); let stmt = build_call( env, call, diff --git a/compiler/mono/src/low_level.rs b/compiler/mono/src/low_level.rs index e95719c7aa..424352f5eb 100644 --- a/compiler/mono/src/low_level.rs +++ b/compiler/mono/src/low_level.rs @@ -2,18 +2,55 @@ use roc_module::symbol::Symbol; #[derive(Clone, Copy, Debug, PartialEq)] pub enum HigherOrder { - ListMap { xs: Symbol }, - ListMap2 { xs: Symbol, ys: Symbol }, - ListMap3 { xs: Symbol, ys: Symbol, zs: Symbol }, - ListMapWithIndex { xs: Symbol }, - ListKeepIf { xs: Symbol }, - ListWalk { xs: Symbol, state: Symbol }, - ListWalkUntil { xs: Symbol, state: Symbol }, - ListWalkBackwards { xs: Symbol, state: Symbol }, - ListKeepOks { xs: Symbol }, - ListKeepErrs { xs: Symbol }, - ListSortWith { xs: Symbol }, - DictWalk { xs: Symbol, state: Symbol }, + ListMap { + xs: Symbol, + }, + ListMap2 { + xs: Symbol, + ys: Symbol, + }, + ListMap3 { + xs: Symbol, + ys: Symbol, + zs: Symbol, + }, + ListMap4 { + xs: Symbol, + ys: Symbol, + zs: Symbol, + ws: Symbol, + }, + ListMapWithIndex { + xs: Symbol, + }, + ListKeepIf { + xs: Symbol, + }, + ListWalk { + xs: Symbol, + state: Symbol, + }, + ListWalkUntil { + xs: Symbol, + state: Symbol, + }, + ListWalkBackwards { + xs: Symbol, + state: Symbol, + }, + ListKeepOks { + xs: Symbol, + }, + ListKeepErrs { + xs: Symbol, + }, + ListSortWith { + xs: Symbol, + }, + DictWalk { + xs: Symbol, + state: Symbol, + }, } impl HigherOrder { @@ -22,6 +59,7 @@ impl HigherOrder { HigherOrder::ListMap { .. } => 1, HigherOrder::ListMap2 { .. } => 2, HigherOrder::ListMap3 { .. } => 3, + HigherOrder::ListMap4 { .. } => 4, HigherOrder::ListMapWithIndex { .. } => 2, HigherOrder::ListKeepIf { .. } => 1, HigherOrder::ListWalk { .. } => 2, @@ -128,202 +166,3 @@ enum FirstOrder { Hash, ExpectTrue, } - -/* -enum FirstOrHigher { - First(FirstOrder), - Higher(HigherOrder), -} - -fn from_low_level(low_level: &LowLevel, arguments: &[Symbol]) -> FirstOrHigher { - use FirstOrHigher::*; - use FirstOrder::*; - use HigherOrder::*; - - match low_level { - LowLevel::StrConcat => First(StrConcat), - LowLevel::StrJoinWith => First(StrJoinWith), - LowLevel::StrIsEmpty => First(StrIsEmpty), - LowLevel::StrStartsWith => First(StrStartsWith), - LowLevel::StrStartsWithCodePt => First(StrStartsWithCodePt), - LowLevel::StrEndsWith => First(StrEndsWith), - LowLevel::StrSplit => First(StrSplit), - LowLevel::StrCountGraphemes => First(StrCountGraphemes), - LowLevel::StrFromInt => First(StrFromInt), - LowLevel::StrFromUtf8 => First(StrFromUtf8), - LowLevel::StrFromUtf8Range => First(StrFromUtf8Range), - LowLevel::StrToUtf8 => First(StrToUtf8), - LowLevel::StrRepeat => First(StrRepeat), - LowLevel::StrFromFloat => First(StrFromFloat), - LowLevel::ListLen => First(ListLen), - LowLevel::ListGetUnsafe => First(ListGetUnsafe), - LowLevel::ListSet => First(ListSet), - LowLevel::ListDrop => First(ListDrop), - LowLevel::ListDropAt => First(ListDropAt), - LowLevel::ListSingle => First(ListSingle), - LowLevel::ListRepeat => First(ListRepeat), - LowLevel::ListReverse => First(ListReverse), - LowLevel::ListConcat => First(ListConcat), - LowLevel::ListContains => First(ListContains), - LowLevel::ListAppend => First(ListAppend), - LowLevel::ListPrepend => First(ListPrepend), - LowLevel::ListJoin => First(ListJoin), - LowLevel::ListRange => First(ListRange), - LowLevel::ListSwap => First(ListSwap), - LowLevel::DictSize => First(DictSize), - LowLevel::DictEmpty => First(DictEmpty), - LowLevel::DictInsert => First(DictInsert), - LowLevel::DictRemove => First(DictRemove), - LowLevel::DictContains => First(DictContains), - LowLevel::DictGetUnsafe => First(DictGetUnsafe), - LowLevel::DictKeys => First(DictKeys), - LowLevel::DictValues => First(DictValues), - LowLevel::DictUnion => First(DictUnion), - LowLevel::DictIntersection => First(DictIntersection), - LowLevel::DictDifference => First(DictDifference), - LowLevel::SetFromList => First(SetFromList), - LowLevel::NumAdd => First(NumAdd), - LowLevel::NumAddWrap => First(NumAddWrap), - LowLevel::NumAddChecked => First(NumAddChecked), - LowLevel::NumSub => First(NumSub), - LowLevel::NumSubWrap => First(NumSubWrap), - LowLevel::NumSubChecked => First(NumSubChecked), - LowLevel::NumMul => First(NumMul), - LowLevel::NumMulWrap => First(NumMulWrap), - LowLevel::NumMulChecked => First(NumMulChecked), - LowLevel::NumGt => First(NumGt), - LowLevel::NumGte => First(NumGte), - LowLevel::NumLt => First(NumLt), - LowLevel::NumLte => First(NumLte), - LowLevel::NumCompare => First(NumCompare), - LowLevel::NumDivUnchecked => First(NumDivUnchecked), - LowLevel::NumRemUnchecked => First(NumRemUnchecked), - LowLevel::NumIsMultipleOf => First(NumIsMultipleOf), - LowLevel::NumAbs => First(NumAbs), - LowLevel::NumNeg => First(NumNeg), - LowLevel::NumSin => First(NumSin), - LowLevel::NumCos => First(NumCos), - LowLevel::NumSqrtUnchecked => First(NumSqrtUnchecked), - LowLevel::NumLogUnchecked => First(NumLogUnchecked), - LowLevel::NumRound => First(NumRound), - LowLevel::NumToFloat => First(NumToFloat), - LowLevel::NumPow => First(NumPow), - LowLevel::NumCeiling => First(NumCeiling), - LowLevel::NumPowInt => First(NumPowInt), - LowLevel::NumFloor => First(NumFloor), - LowLevel::NumIsFinite => First(NumIsFinite), - LowLevel::NumAtan => First(NumAtan), - LowLevel::NumAcos => First(NumAcos), - LowLevel::NumAsin => First(NumAsin), - LowLevel::NumBitwiseAnd => First(NumBitwiseAnd), - LowLevel::NumBitwiseXor => First(NumBitwiseXor), - LowLevel::NumBitwiseOr => First(NumBitwiseOr), - LowLevel::NumShiftLeftBy => First(NumShiftLeftBy), - LowLevel::NumShiftRightBy => First(NumShiftRightBy), - LowLevel::NumBytesToU16 => First(NumBytesToU16), - LowLevel::NumBytesToU32 => First(NumBytesToU32), - LowLevel::NumShiftRightZfBy => First(NumShiftRightZfBy), - LowLevel::NumIntCast => First(NumIntCast), - LowLevel::Eq => First(Eq), - LowLevel::NotEq => First(NotEq), - LowLevel::And => First(And), - LowLevel::Or => First(Or), - LowLevel::Not => First(Not), - LowLevel::Hash => First(Hash), - LowLevel::ExpectTrue => First(ExpectTrue), - LowLevel::ListMap => { - debug_assert_eq!(arguments.len(), 3); - Higher(ListMap { - xs: arguments[0], - function_name: arguments[1], - function_env: arguments[2], - }) - } - LowLevel::ListMap2 => { - debug_assert_eq!(arguments.len(), 4); - Higher(ListMap2 { - xs: arguments[0], - ys: arguments[1], - function_name: arguments[2], - function_env: arguments[3], - }) - } - LowLevel::ListMap3 => { - debug_assert_eq!(arguments.len(), 5); - Higher(ListMap3 { - xs: arguments[0], - ys: arguments[1], - zs: arguments[2], - function_name: arguments[3], - function_env: arguments[4], - }) - } - LowLevel::ListMapWithIndex => { - debug_assert_eq!(arguments.len(), 3); - Higher(ListMapWithIndex { - xs: arguments[0], - function_name: arguments[1], - function_env: arguments[2], - }) - } - LowLevel::ListKeepIf => { - debug_assert_eq!(arguments.len(), 3); - Higher(ListKeepIf { - xs: arguments[0], - function_name: arguments[1], - function_env: arguments[2], - }) - } - LowLevel::ListWalk => { - debug_assert_eq!(arguments.len(), 3); - Higher(ListWalk { - xs: arguments[0], - function_name: arguments[1], - function_env: arguments[2], - }) - } - LowLevel::ListWalkUntil => { - debug_assert_eq!(arguments.len(), 3); - Higher(ListWalkUntil { - function_name: arguments[1], - function_env: arguments[2], - }) - } - LowLevel::ListWalkBackwards => { - debug_assert_eq!(arguments.len(), 3); - Higher(ListWalkBackwards { - function_name: arguments[1], - function_env: arguments[2], - }) - } - LowLevel::ListKeepOks => { - debug_assert_eq!(arguments.len(), 3); - Higher(ListKeepOks { - function_name: arguments[1], - function_env: arguments[2], - }) - } - LowLevel::ListKeepErrs => { - debug_assert_eq!(arguments.len(), 3); - Higher(ListKeepErrs { - function_name: arguments[1], - function_env: arguments[2], - }) - } - LowLevel::ListSortWith => { - debug_assert_eq!(arguments.len(), 3); - Higher(ListSortWith { - function_name: arguments[1], - function_env: arguments[2], - }) - } - LowLevel::DictWalk => { - debug_assert_eq!(arguments.len(), 3); - Higher(DictWalk { - function_name: arguments[1], - function_env: arguments[2], - }) - } - } -} -*/ diff --git a/compiler/parse/src/blankspace.rs b/compiler/parse/src/blankspace.rs index a5c353fecb..ff10152e84 100644 --- a/compiler/parse/src/blankspace.rs +++ b/compiler/parse/src/blankspace.rs @@ -1,9 +1,7 @@ use crate::ast::CommentOrNewline; use crate::ast::Spaceable; use crate::parser::{ - self, and, backtrackable, BadInputError, Col, Parser, - Progress::{self, *}, - Row, State, + self, and, backtrackable, BadInputError, Col, Parser, Progress::*, Row, State, }; use bumpalo::collections::vec::Vec; use bumpalo::Bump; @@ -181,109 +179,6 @@ where spaces_help_help(min_indent, space_problem, indent_problem) } -pub fn spaces_till_end_of_line<'a, E: 'a>( - tab_problem: fn(Row, Col) -> E, -) -> impl Parser<'a, Option<&'a str>, E> { - move |_, mut state: State<'a>| { - let mut bytes = state.bytes; - let mut row = state.line; - let mut col = state.column; - - for c in bytes { - match c { - b' ' => { - bytes = &bytes[1..]; - col += 1; - } - b'\n' => { - bytes = &bytes[1..]; - row += 1; - col = 0; - - state.line = row; - state.column = col; - state.bytes = bytes; - - return Ok((MadeProgress, None, state)); - } - b'\r' => { - bytes = &bytes[1..]; - } - b'\t' => { - return Err(( - MadeProgress, - tab_problem(row, col), - State { - line: row, - column: col, - ..state - }, - )) - } - b'#' => match chomp_line_comment(bytes) { - Ok(comment) => { - state.line += 1; - state.column = 0; - - let width = 1 + comment.len(); - if let Some(b'\n') = bytes.get(width) { - state.bytes = &bytes[width + 1..]; - } else { - state.bytes = &bytes[width..]; - } - - return Ok((MadeProgress, Some(comment), state)); - } - Err(_) => unreachable!("we check the first character is a #"), - }, - _ => break, - } - } - - if state.column == col { - Ok((NoProgress, None, state)) - } else { - Ok(( - MadeProgress, - None, - State { - column: col, - bytes, - ..state - }, - )) - } - } -} - -fn chomp_line_comment(buffer: &[u8]) -> Result<&str, Progress> { - if let Some(b'#') = buffer.get(0) { - if (&buffer[1..]).starts_with(b"# ") { - // this is a doc comment, not a line comment - Err(NoProgress) - } else { - use encode_unicode::CharExt; - - let mut chomped = 1; - - while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) { - if ch == '\n' { - break; - } else { - chomped += width; - } - } - - let comment_bytes = &buffer[1..chomped]; - let comment = unsafe { std::str::from_utf8_unchecked(comment_bytes) }; - - Ok(comment) - } - } else { - Err(NoProgress) - } -} - #[inline(always)] fn spaces_help_help<'a, E>( min_indent: u16, diff --git a/compiler/problem/Cargo.toml b/compiler/problem/Cargo.toml index bb1d4456ea..c2810429d1 100644 --- a/compiler/problem/Cargo.toml +++ b/compiler/problem/Cargo.toml @@ -13,7 +13,6 @@ roc_parse = { path = "../parse" } [dev-dependencies] pretty_assertions = "0.5.1" -maplit = "1.0.1" indoc = "0.3.3" quickcheck = "0.8" quickcheck_macros = "0.8" diff --git a/compiler/reporting/Cargo.toml b/compiler/reporting/Cargo.toml index dd14385c12..db90aadbc1 100644 --- a/compiler/reporting/Cargo.toml +++ b/compiler/reporting/Cargo.toml @@ -27,7 +27,6 @@ roc_builtins = { path = "../builtins" } roc_problem = { path = "../problem" } roc_parse = { path = "../parse" } pretty_assertions = "0.5.1" -maplit = "1.0.1" indoc = "0.3.3" quickcheck = "0.8" quickcheck_macros = "0.8" diff --git a/compiler/solve/Cargo.toml b/compiler/solve/Cargo.toml index fef06876a7..a41b1e67d7 100644 --- a/compiler/solve/Cargo.toml +++ b/compiler/solve/Cargo.toml @@ -20,7 +20,6 @@ roc_problem = { path = "../problem" } roc_parse = { path = "../parse" } roc_solve = { path = "../solve" } pretty_assertions = "0.5.1" -maplit = "1.0.1" indoc = "0.3.3" tempfile = "3.1.0" quickcheck = "0.8" diff --git a/compiler/str/Cargo.toml b/compiler/str/Cargo.toml index bf5ce8e129..68e5cfde2e 100644 --- a/compiler/str/Cargo.toml +++ b/compiler/str/Cargo.toml @@ -21,7 +21,6 @@ roc_builtins = { path = "../builtins" } roc_parse = { path = "../parse" } roc_solve = { path = "../solve" } pretty_assertions = "0.5.1" -maplit = "1.0.1" indoc = "0.3.3" quickcheck = "0.8" quickcheck_macros = "0.8" diff --git a/compiler/test_gen/Cargo.toml b/compiler/test_gen/Cargo.toml index ab08fc1527..9bc5e00ed9 100644 --- a/compiler/test_gen/Cargo.toml +++ b/compiler/test_gen/Cargo.toml @@ -28,7 +28,6 @@ im = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version! bumpalo = { version = "3.6.1", features = ["collections"] } either = "1.6.1" -indoc = "0.3.3" libc = "0.2" inkwell = { path = "../../vendor/inkwell" } target-lexicon = "0.12.2" @@ -38,11 +37,11 @@ wasmer-wasi = "2.0.0" tempfile = "3.1.0" [dev-dependencies] -maplit = "1.0.1" quickcheck = "0.8" quickcheck_macros = "0.8" tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] } bumpalo = { version = "3.6.1", features = ["collections"] } +indoc = "0.3.3" [features] default = [] diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 27006abdf3..e483bc6e85 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -31,9 +31,9 @@ pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { use roc_gen_llvm::llvm::build::PanicTagId; - use libc::c_char; use std::convert::TryFrom; use std::ffi::CStr; + use std::os::raw::c_char; match PanicTagId::try_from(tag_id) { Ok(PanicTagId::NullTerminatedString) => { @@ -763,6 +763,37 @@ fn list_map_closure() { ); } +#[test] +fn list_map4_group() { + assert_evals_to!( + indoc!( + r#" + List.map4 [1,2,3] [3,2,1] [2,1,3] [3,1,2] (\a, b, c, d -> Group a b c d) + "# + ), + RocList::from_slice(&[(1, 3, 2, 3), (2, 2, 1, 1), (3, 1, 3, 2)]), + RocList<(i64, i64, i64, i64)> + ); +} + +#[test] +fn list_map4_different_length() { + assert_evals_to!( + indoc!( + r#" + List.map4 + ["h", "i", "j", "k"] + ["o", "p", "q"] + ["l", "m"] + ["a"] + (\a, b, c, d -> Str.concat a (Str.concat b (Str.concat c d))) + "# + ), + RocList::from_slice(&[RocStr::from_slice("hola".as_bytes()),]), + RocList + ); +} + #[test] fn list_map3_group() { assert_evals_to!( diff --git a/compiler/test_gen/src/helpers/eval.rs b/compiler/test_gen/src/helpers/eval.rs index be01cdc7ab..e844d420ea 100644 --- a/compiler/test_gen/src/helpers/eval.rs +++ b/compiler/test_gen/src/helpers/eval.rs @@ -434,8 +434,8 @@ fn wasm_roc_panic(address: u32, tag_id: u32) { let width = 100; let c_ptr = (ptr.deref(memory, 0, width)).unwrap(); - use libc::c_char; use std::ffi::CStr; + use std::os::raw::c_char; let slice = unsafe { CStr::from_ptr(c_ptr as *const _ as *const c_char) }; string = slice.to_str().unwrap(); }); diff --git a/compiler/test_mono/Cargo.toml b/compiler/test_mono/Cargo.toml index 3167bbc789..2fbfe29c61 100644 --- a/compiler/test_mono/Cargo.toml +++ b/compiler/test_mono/Cargo.toml @@ -5,35 +5,16 @@ authors = ["The Roc Contributors"] license = "UPL-1.0" edition = "2018" -[dependencies] +[dev-dependencies] roc_collections = { path = "../collections" } -roc_region = { path = "../region" } roc_module = { path = "../module" } -roc_problem = { path = "../problem" } -roc_types = { path = "../types" } roc_builtins = { path = "../builtins" } -roc_constrain = { path = "../constrain" } -roc_unify = { path = "../unify" } -roc_solve = { path = "../solve" } -roc_reporting = { path = "../reporting" } roc_load = { path = "../load" } roc_can = { path = "../can" } -roc_parse = { path = "../parse" } -roc_build = { path = "../build" } roc_mono = { path = "../mono" } test_mono_macros = { path = "../test_mono_macros" } -im = "14" # im and im-rc should always have the same version! -im-rc = "14" # im and im-rc should always have the same version! -bumpalo = { version = "3.6.1", features = ["collections"] } -either = "1.6.1" -indoc = "0.3.3" -libc = "0.2" -target-lexicon = "0.12.2" -libloading = "0.6" - -[dev-dependencies] pretty_assertions = "0.5.1" -indoc = "0.3.3" quickcheck = "0.8" quickcheck_macros = "0.8" bumpalo = { version = "3.6.1", features = ["collections"] } +indoc = "0.3.3" diff --git a/compiler/test_mono_macros/Cargo.toml b/compiler/test_mono_macros/Cargo.toml index 24ddaa408b..dcf87eb915 100644 --- a/compiler/test_mono_macros/Cargo.toml +++ b/compiler/test_mono_macros/Cargo.toml @@ -11,5 +11,4 @@ proc-macro = true [dependencies] syn = { version = "1.0.39", features = ["full", "extra-traits"] } quote = "1.0.7" -darling = "0.10.2" proc-macro2 = "1.0.24" diff --git a/compiler/test_wasm/Cargo.toml b/compiler/test_wasm/Cargo.toml new file mode 100644 index 0000000000..e8370bc960 --- /dev/null +++ b/compiler/test_wasm/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "test_wasm" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# roc_module = { path = "../module" } +# roc_mono = { path = "../mono" } + +# # TODO: switch to parity-wasm 0.44 once it's out (allows bumpalo vectors in some places) +parity-wasm = { git = "https://github.com/brian-carroll/parity-wasm", branch = "master" } + +wasmer = "2.0.0" +wasmer-wasi = "2.0.0" + +roc_collections = { path = "../collections" } +roc_std = { path = "../../roc_std" } +bumpalo = { version = "3.6.1", features = ["collections"] } +roc_gen_wasm = { path = "../gen_wasm" } +roc_can = { path = "../can" } +roc_builtins = { path = "../builtins" } +roc_load = { path = "../load" } +roc_types = { path = "../types" } +roc_module = { path = "../module" } +indoc = "0.3.3" +pretty_assertions = "0.5.1" +libc = "0.2" +target-lexicon = "0.12.2" +tempfile = "3.1.0" diff --git a/compiler/gen_wasm/tests/helpers/eval.rs b/compiler/test_wasm/src/helpers/eval.rs similarity index 94% rename from compiler/gen_wasm/tests/helpers/eval.rs rename to compiler/test_wasm/src/helpers/eval.rs index e4bbbd71da..0ee845f592 100644 --- a/compiler/gen_wasm/tests/helpers/eval.rs +++ b/compiler/test_wasm/src/helpers/eval.rs @@ -67,18 +67,12 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( use roc_load::file::MonomorphizedModule; let MonomorphizedModule { - procedures: top_procedures, + procedures, interns, exposed_to_host, .. } = loaded; - let mut procedures = MutMap::default(); - - for (key, proc) in top_procedures { - procedures.insert(key, proc); - } - // You can comment and uncomment this block out to get more useful information // while you're working on the wasm backend! // { @@ -95,6 +89,13 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( // println!("=================================\n"); // } + debug_assert_eq!(exposed_to_host.len(), 1); + let main_fn_symbol = loaded.entry_point.symbol; + let main_fn_index = procedures + .keys() + .position(|(s, _)| *s == main_fn_symbol) + .unwrap(); + let exposed_to_host = exposed_to_host.keys().copied().collect::>(); let env = roc_gen_wasm::Env { @@ -103,7 +104,7 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( exposed_to_host, }; - let (mut builder, mut code_section_bytes, main_function_index) = + let (mut builder, mut code_section_bytes) = roc_gen_wasm::build_module_help(&env, procedures).unwrap(); T::insert_test_wrapper( @@ -111,14 +112,14 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( &mut builder, &mut code_section_bytes, TEST_WRAPPER_NAME, - main_function_index, + main_fn_index as u32, ); let mut parity_module = builder.build(); replace_code_section(&mut parity_module, code_section_bytes); let module_bytes = parity_module.into_bytes().unwrap(); - // for debugging (e.g. with wasm2wat) + // for debugging (e.g. with wasm2wat or wasm-objdump) if false { use std::io::Write; diff --git a/compiler/gen_wasm/tests/helpers/mod.rs b/compiler/test_wasm/src/helpers/mod.rs similarity index 98% rename from compiler/gen_wasm/tests/helpers/mod.rs rename to compiler/test_wasm/src/helpers/mod.rs index c28fee53d9..5627bb930a 100644 --- a/compiler/gen_wasm/tests/helpers/mod.rs +++ b/compiler/test_wasm/src/helpers/mod.rs @@ -1,5 +1,3 @@ -extern crate bumpalo; - #[macro_use] pub mod eval; pub mod wasm32_test_result; diff --git a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs b/compiler/test_wasm/src/helpers/wasm32_test_result.rs similarity index 93% rename from compiler/gen_wasm/tests/helpers/wasm32_test_result.rs rename to compiler/test_wasm/src/helpers/wasm32_test_result.rs index 629eeccac2..dfd78b88fb 100644 --- a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs +++ b/compiler/test_wasm/src/helpers/wasm32_test_result.rs @@ -3,7 +3,7 @@ use parity_wasm::elements::Internal; use roc_gen_wasm::code_builder::{Align, CodeBuilder, ValueType}; use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory; -use roc_gen_wasm::{overwrite_padded_u32, LocalId}; +use roc_gen_wasm::{serialize::SerialBuffer, LocalId}; use roc_std::{RocDec, RocList, RocOrder, RocStr}; pub trait Wasm32TestResult { @@ -30,15 +30,15 @@ pub trait Wasm32TestResult { let mut code_builder = CodeBuilder::new(arena); Self::build_wrapper_body(&mut code_builder, main_function_index); - code_builder.serialize(code_section_bytes).unwrap(); + code_builder.serialize(code_section_bytes); let mut num_procs = 0; for (i, byte) in code_section_bytes[5..10].iter().enumerate() { num_procs += ((byte & 0x7f) as u32) << (i * 7); } let inner_length = (code_section_bytes.len() - 5) as u32; - overwrite_padded_u32(&mut code_section_bytes[0..5], inner_length); - overwrite_padded_u32(&mut code_section_bytes[5..10], num_procs + 1); + code_section_bytes.overwrite_padded_u32(0, inner_length); + code_section_bytes.overwrite_padded_u32(5, num_procs + 1); } fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32); @@ -53,7 +53,8 @@ macro_rules! build_wrapper_body_primitive { let frame_size = 8; code_builder.get_local(frame_pointer_id); - code_builder.call(main_function_index, 0, true); + // Raw "call" instruction. Don't bother with symbol & relocation since we're not going to link. + code_builder.inst_imm32(roc_gen_wasm::opcodes::CALL, 0, true, main_function_index); code_builder.$store_instruction($align, 0); code_builder.get_local(frame_pointer_id); @@ -80,7 +81,8 @@ fn build_wrapper_body_stack_memory( let frame_pointer = Some(local_id); code_builder.get_local(local_id); - code_builder.call(main_function_index, 0, true); + // Raw "call" instruction. Don't bother with symbol & relocation since we're not going to link. + code_builder.inst_imm32(roc_gen_wasm::opcodes::CALL, 0, true, main_function_index); code_builder.get_local(local_id); code_builder.finalize(local_types, size as i32, frame_pointer); } diff --git a/compiler/test_wasm/src/lib.rs b/compiler/test_wasm/src/lib.rs new file mode 100644 index 0000000000..089dcb3f36 --- /dev/null +++ b/compiler/test_wasm/src/lib.rs @@ -0,0 +1,3 @@ +mod helpers; +pub mod wasm_num; +pub mod wasm_records; diff --git a/compiler/test_wasm/src/wasm_num.rs b/compiler/test_wasm/src/wasm_num.rs new file mode 100644 index 0000000000..7d1ebf1f2c --- /dev/null +++ b/compiler/test_wasm/src/wasm_num.rs @@ -0,0 +1,1052 @@ +#![cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))] +use crate::assert_evals_to; +use indoc::indoc; + +#[test] +fn i64_values() { + assert_evals_to!("0", 0, i64); + assert_evals_to!("-0", 0, i64); + assert_evals_to!("-1", -1, i64); + assert_evals_to!("1", 1, i64); + assert_evals_to!("9_000_000_000_000", 9_000_000_000_000, i64); + assert_evals_to!("-9_000_000_000_000", -9_000_000_000_000, i64); + assert_evals_to!("0b1010", 0b1010, i64); + assert_evals_to!("0o17", 0o17, i64); + assert_evals_to!("0x1000_0000_0000_0000", 0x1000_0000_0000_0000, i64); +} + +#[test] +fn f64_values() { + assert_evals_to!("0.0", 0.0, f64); + assert_evals_to!("-0.0", 0.0, f64); + assert_evals_to!("1.0", 1.0, f64); + assert_evals_to!("-1.0", -1.0, f64); + assert_evals_to!("3.1415926535897932", 3.141_592_653_589_793, f64); + assert_evals_to!(&format!("{:0.1}", f64::MIN), f64::MIN, f64); + assert_evals_to!(&format!("{:0.1}", f64::MAX), f64::MAX, f64); +} + +#[test] +fn i8_add_wrap() { + assert_evals_to!( + indoc!( + r#" + x : I8 + x = 0x7f + 0x7f + + x + "# + ), + -2, + i8 + ); +} + +#[test] +fn i16_add_wrap() { + assert_evals_to!( + indoc!( + r#" + x : I16 + x = 0x7fff + 0x7fff + + x + "# + ), + -2, + i16 + ); +} + +#[test] +fn i32_add_wrap() { + assert_evals_to!( + indoc!( + r#" + x : I32 + x = 0x7fffffff + 0x7fffffff + + x + "# + ), + -2, + i32 + ); +} + +#[test] +fn u8_add_wrap() { + assert_evals_to!( + indoc!( + r#" + x : U8 + x = 0xff + 0xff + + x + "# + ), + 0xfe, + u8 + ); +} + +#[test] +fn u16_add_wrap() { + assert_evals_to!( + indoc!( + r#" + x : U16 + x = 0xffff + 0xffff + + x + "# + ), + 0xfffe, + u16 + ); +} +#[test] +fn u32_add_wrap() { + assert_evals_to!( + indoc!( + r#" + x : U32 + x = 0xffffffff + 0xffffffff + + x + "# + ), + 0xfffffffe, + u32 + ); +} + +#[test] +fn gen_add_i64() { + assert_evals_to!( + indoc!( + r#" + 1 + 2 + 3 + "# + ), + 6, + i64 + ); +} + +#[test] +fn if_then_else() { + assert_evals_to!( + indoc!( + r#" + cond : Bool + cond = True + + if cond then + 0 + else + 1 + "# + ), + 0, + i64 + ); +} + +#[test] +fn rgb_red() { + assert_evals_to!( + indoc!( + r#" + when Red is + Red -> 111 + Green -> 222 + Blue -> 333 + "# + ), + 111, + i64 + ); +} + +#[test] +fn rgb_green() { + assert_evals_to!( + indoc!( + r#" + when Green is + Red -> 111 + Green -> 222 + Blue -> 333 + "# + ), + 222, + i64 + ); +} + +#[test] +fn rgb_blue() { + assert_evals_to!( + indoc!( + r#" + when Blue is + Red -> 111 + Green -> 222 + Blue -> 333 + "# + ), + 333, + i64 + ); +} + +#[test] +fn join_point() { + assert_evals_to!( + indoc!( + r#" + x = if True then 111 else 222 + + x + 123 + "# + ), + 234, + i64 + ); +} + +#[test] +fn factorial() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + fac : I32, I32 -> I32 + fac = \n, accum -> + if n > 1 then + fac (n - 1) (n * accum) + else + accum + + main : I32 + main = fac 8 1 + "# + ), + 40_320, + i32 + ); +} + +#[test] +fn gen_add_f64() { + assert_evals_to!( + indoc!( + r#" + 1.1 + 2.4 + 3 + "# + ), + 6.5, + f64 + ); +} + +#[test] +fn gen_sub_i64() { + assert_evals_to!( + indoc!( + r#" + 1 - 2 - 3 + "# + ), + -4, + i64 + ); +} + +#[test] +fn gen_mul_i64() { + assert_evals_to!( + indoc!( + r#" + 2 * 4 * 6 + "# + ), + 48, + i64 + ); +} + +#[test] +fn i64_force_stack() { + // This claims 33 registers. One more than Arm and RISC-V, and many more than x86-64. + assert_evals_to!( + indoc!( + r#" + a = 0 + b = 1 + c = 2 + d = 3 + e = 4 + f = 5 + g = 6 + h = 7 + i = 8 + j = 9 + k = 10 + l = 11 + m = 12 + n = 13 + o = 14 + p = 15 + q = 16 + r = 17 + s = 18 + t = 19 + u = 20 + v = 21 + w = 22 + x = 23 + y = 24 + z = 25 + aa = 26 + ab = 27 + ac = 28 + ad = 29 + ae = 30 + af = 31 + ag = 32 + + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + aa + ab + ac + ad + ae + af + ag + "# + ), + 528, + i64 + ); +} + +// #[test] +// fn i64_abs() { +// assert_evals_to!("Num.abs -6", 6, i64); +// assert_evals_to!("Num.abs 7", 7, i64); +// assert_evals_to!("Num.abs 0", 0, i64); +// assert_evals_to!("Num.abs -0", 0, i64); +// assert_evals_to!("Num.abs -1", 1, i64); +// assert_evals_to!("Num.abs 1", 1, i64); +// assert_evals_to!("Num.abs 9_000_000_000_000", 9_000_000_000_000, i64); +// assert_evals_to!("Num.abs -9_000_000_000_000", 9_000_000_000_000, i64); +// } + +// #[test] +// fn gen_int_eq() { +// assert_evals_to!( +// indoc!( +// r#" +// 4 == 4 +// "# +// ), +// true, +// bool +// ); + +// assert_evals_to!( +// indoc!( +// r#" +// 3 == 4 +// "# +// ), +// false, +// bool +// ); +// } + +// #[test] +// fn gen_basic_fn() { +// assert_evals_to!( +// indoc!( +// r#" +// always42 : Num.Num (Num.Integer Num.Signed64) -> Num.Num (Num.Integer Num.Signed64) +// always42 = \_ -> 42 + +// always42 5 +// "# +// ), +// 42, +// i64 +// ); +// } + +// #[test] +// fn gen_wrap_add_nums() { +// assert_evals_to!( +// indoc!( +// r#" +// add2 = \num1, num2 -> num1 + num2 + +// add2 4 5 +// "# +// ), +// 9, +// i64 +// ); +// } + +// #[test] +// fn gen_wrap_add_nums_force_stack() { +// assert_evals_to!( +// indoc!( +// r#" +// add9 = \num1, num2, num3, num4, num5, num6, num7, num8, num9 -> num1 + num2 + num3 + num4 + num5 + num6 + num7 + num8 + num9 + +// add9 1 2 3 4 5 6 7 8 9 +// "# +// ), +// 45, +// i64 +// ); +// } + +// #[test] +// fn pow_int() { +// assert_evals_to!("Num.powInt 2 3", 8, i64); +// } + +// #[test] +// fn acos() { +// assert_evals_to!("Num.acos 0.5", 1.0471975511965979, f64); +// } + +// #[test] +// fn asin() { +// assert_evals_to!("Num.asin 0.5", 0.5235987755982989, f64); +// } + +// #[test] +// fn atan() { +// assert_evals_to!("Num.atan 10", 1.4711276743037347, f64); +// } + +// #[test] +// fn gen_if_fn() { +// assert_evals_to!( +// indoc!( +// r#" +// limitedNegate = \num -> +// x = +// if num == 1 then +// -1 +// else if num == -1 then +// 1 +// else +// num +// x + +// limitedNegate 1 +// "# +// ), +// -1, +// i64 +// ); +// } + +// #[test] +// fn gen_fib_fn() { +// assert_evals_to!( +// indoc!( +// r#" +// fib = \n -> +// if n == 0 then +// 0 +// else if n == 1 then +// 1 +// else +// (fib (n - 1)) + (fib (n - 2)) + +// fib 10 +// "# +// ), +// 55, +// i64 +// ); +// } + +// #[test] +// fn f64_abs() { +// assert_evals_to!("Num.abs -4.7", 4.7, f64); +// assert_evals_to!("Num.abs 5.8", 5.8, f64); +// } + +// #[test] +// fn f64_round() { +// assert_evals_to!("Num.round 3.6", 4, i64); +// assert_evals_to!("Num.round 3.4", 3, i64); +// assert_evals_to!("Num.round 2.5", 3, i64); +// assert_evals_to!("Num.round -2.3", -2, i64); +// assert_evals_to!("Num.round -2.5", -3, i64); +// } + +// #[test] +// fn f64_sqrt() { +// // FIXME this works with normal types, but fails when checking uniqueness types +// assert_evals_to!( +// indoc!( +// r#" +// when Num.sqrt 100 is +// Ok val -> val +// Err _ -> -1 +// "# +// ), +// 10.0, +// f64 +// ); +// } + +// #[test] +// fn gen_float_eq() { +// assert_evals_to!( +// indoc!( +// r#" +// 1.0 == 1.0 +// "# +// ), +// true, +// bool +// ); +// } + +// #[test] +// fn gen_div_f64() { +// // FIXME this works with normal types, but fails when checking uniqueness types +// assert_evals_to!( +// indoc!( +// r#" +// when 48 / 2 is +// Ok val -> val +// Err _ -> -1 +// "# +// ), +// 24.0, +// f64 +// ); +// } + +// #[test] +// fn gen_int_neq() { +// assert_evals_to!( +// indoc!( +// r#" +// 4 != 5 +// "# +// ), +// true, +// bool +// ); +// } + +// #[test] +// fn gen_wrap_int_neq() { +// assert_evals_to!( +// indoc!( +// r#" +// wrappedNotEq : a, a -> Bool +// wrappedNotEq = \num1, num2 -> +// num1 != num2 + +// wrappedNotEq 2 3 +// "# +// ), +// true, +// bool +// ); +// } + +#[test] +fn gen_sub_f64() { + assert_evals_to!( + indoc!( + r#" + 1.5 - 2.4 - 3 + "# + ), + -3.9, + f64 + ); +} + +// #[test] +// fn gen_div_i64() { +// assert_evals_to!( +// indoc!( +// r#" +// when 1000 // 10 is +// Ok val -> val +// Err _ -> -1 +// "# +// ), +// 100, +// i64 +// ); +// } + +// #[test] +// fn gen_div_by_zero_i64() { +// assert_evals_to!( +// indoc!( +// r#" +// when 1000 // 0 is +// Err DivByZero -> 99 +// _ -> -24 +// "# +// ), +// 99, +// i64 +// ); +// } + +// #[test] +// fn gen_rem_i64() { +// assert_evals_to!( +// indoc!( +// r#" +// when Num.rem 8 3 is +// Ok val -> val +// Err _ -> -1 +// "# +// ), +// 2, +// i64 +// ); +// } + +// #[test] +// fn gen_rem_div_by_zero_i64() { +// assert_evals_to!( +// indoc!( +// r#" +// when Num.rem 8 0 is +// Err DivByZero -> 4 +// Ok _ -> -23 +// "# +// ), +// 4, +// i64 +// ); +// } + +// #[test] +// fn gen_is_zero_i64() { +// assert_evals_to!("Num.isZero 0", true, bool); +// assert_evals_to!("Num.isZero 1", false, bool); +// } + +// #[test] +// fn gen_is_positive_i64() { +// assert_evals_to!("Num.isPositive 0", false, bool); +// assert_evals_to!("Num.isPositive 1", true, bool); +// assert_evals_to!("Num.isPositive -5", false, bool); +// } + +// #[test] +// fn gen_is_negative_i64() { +// assert_evals_to!("Num.isNegative 0", false, bool); +// assert_evals_to!("Num.isNegative 3", false, bool); +// assert_evals_to!("Num.isNegative -2", true, bool); +// } + +// #[test] +// fn gen_is_positive_f64() { +// assert_evals_to!("Num.isPositive 0.0", false, bool); +// assert_evals_to!("Num.isPositive 4.7", true, bool); +// assert_evals_to!("Num.isPositive -8.5", false, bool); +// } + +// #[test] +// fn gen_is_negative_f64() { +// assert_evals_to!("Num.isNegative 0.0", false, bool); +// assert_evals_to!("Num.isNegative 9.9", false, bool); +// assert_evals_to!("Num.isNegative -4.4", true, bool); +// } + +// #[test] +// fn gen_is_zero_f64() { +// assert_evals_to!("Num.isZero 0", true, bool); +// assert_evals_to!("Num.isZero 0_0", true, bool); +// assert_evals_to!("Num.isZero 0.0", true, bool); +// assert_evals_to!("Num.isZero 1", false, bool); +// } + +// #[test] +// fn gen_is_odd() { +// assert_evals_to!("Num.isOdd 4", false, bool); +// assert_evals_to!("Num.isOdd 5", true, bool); +// } + +// #[test] +// fn gen_is_even() { +// assert_evals_to!("Num.isEven 6", true, bool); +// assert_evals_to!("Num.isEven 7", false, bool); +// } + +// #[test] +// fn sin() { +// assert_evals_to!("Num.sin 0", 0.0, f64); +// assert_evals_to!("Num.sin 1.41421356237", 0.9877659459922529, f64); +// } + +// #[test] +// fn cos() { +// assert_evals_to!("Num.cos 0", 1.0, f64); +// assert_evals_to!("Num.cos 3.14159265359", -1.0, f64); +// } + +// #[test] +// fn tan() { +// assert_evals_to!("Num.tan 0", 0.0, f64); +// assert_evals_to!("Num.tan 1", 1.557407724654902, f64); +// } + +// #[test] +// fn lt_i64() { +// assert_evals_to!("1 < 2", true, bool); +// assert_evals_to!("1 < 1", false, bool); +// assert_evals_to!("2 < 1", false, bool); +// assert_evals_to!("0 < 0", false, bool); +// } + +// #[test] +// fn lte_i64() { +// assert_evals_to!("1 <= 1", true, bool); +// assert_evals_to!("2 <= 1", false, bool); +// assert_evals_to!("1 <= 2", true, bool); +// assert_evals_to!("0 <= 0", true, bool); +// } + +// #[test] +// fn gt_i64() { +// assert_evals_to!("2 > 1", true, bool); +// assert_evals_to!("2 > 2", false, bool); +// assert_evals_to!("1 > 1", false, bool); +// assert_evals_to!("0 > 0", false, bool); +// } + +// #[test] +// fn gte_i64() { +// assert_evals_to!("1 >= 1", true, bool); +// assert_evals_to!("1 >= 2", false, bool); +// assert_evals_to!("2 >= 1", true, bool); +// assert_evals_to!("0 >= 0", true, bool); +// } + +// #[test] +// fn lt_f64() { +// assert_evals_to!("1.1 < 1.2", true, bool); +// assert_evals_to!("1.1 < 1.1", false, bool); +// assert_evals_to!("1.2 < 1.1", false, bool); +// assert_evals_to!("0.0 < 0.0", false, bool); +// } + +// #[test] +// fn lte_f64() { +// assert_evals_to!("1.1 <= 1.1", true, bool); +// assert_evals_to!("1.2 <= 1.1", false, bool); +// assert_evals_to!("1.1 <= 1.2", true, bool); +// assert_evals_to!("0.0 <= 0.0", true, bool); +// } + +// #[test] +// fn gt_f64() { +// assert_evals_to!("2.2 > 1.1", true, bool); +// assert_evals_to!("2.2 > 2.2", false, bool); +// assert_evals_to!("1.1 > 2.2", false, bool); +// assert_evals_to!("0.0 > 0.0", false, bool); +// } + +// #[test] +// fn gte_f64() { +// assert_evals_to!("1.1 >= 1.1", true, bool); +// assert_evals_to!("1.1 >= 1.2", false, bool); +// assert_evals_to!("1.2 >= 1.1", true, bool); +// assert_evals_to!("0.0 >= 0.0", true, bool); +// } + +#[test] +fn gen_order_of_arithmetic_ops() { + assert_evals_to!( + indoc!( + r#" + 1 + 3 * 7 - 2 + "# + ), + 20, + i64 + ); +} + +#[test] +fn gen_order_of_arithmetic_ops_complex_float() { + assert_evals_to!( + indoc!( + r#" + 3 - 48 * 2.0 + "# + ), + -93.0, + f64 + ); +} + +// #[test] +// fn if_guard_bind_variable_false() { +// assert_evals_to!( +// indoc!( +// r#" +// wrapper = \{} -> +// when 10 is +// x if x == 5 -> 0 +// _ -> 42 + +// wrapper {} +// "# +// ), +// 42, +// i64 +// ); +// } + +// #[test] +// fn if_guard_bind_variable_true() { +// assert_evals_to!( +// indoc!( +// r#" +// wrapper = \{} -> +// when 10 is +// x if x == 10 -> 42 +// _ -> 0 + +// wrapper {} +// "# +// ), +// 42, +// i64 +// ); +// } + +// #[test] +// fn tail_call_elimination() { +// assert_evals_to!( +// indoc!( +// r#" +// sum = \n, accum -> +// when n is +// 0 -> accum +// _ -> sum (n - 1) (n + accum) + +// sum 1_000_000 0 +// "# +// ), +// 500000500000, +// i64 +// ); +// } + +// #[test] +// fn int_negate() { +// assert_evals_to!("Num.neg 123", -123, i64); +// } + +// #[test] +// fn gen_wrap_int_neg() { +// assert_evals_to!( +// indoc!( +// r#" +// wrappedNeg = \num -> -num + +// wrappedNeg 3 +// "# +// ), +// -3, +// i64 +// ); +// } + +// #[test] +// fn int_to_float() { +// assert_evals_to!("Num.toFloat 0x9", 9.0, f64); +// } + +// #[test] +// fn num_to_float() { +// assert_evals_to!("Num.toFloat 9", 9.0, f64); +// } + +// #[test] +// fn float_to_float() { +// assert_evals_to!("Num.toFloat 0.5", 0.5, f64); +// } + +// #[test] +// fn int_compare() { +// assert_evals_to!("Num.compare 0 1", RocOrder::Lt, RocOrder); +// assert_evals_to!("Num.compare 1 1", RocOrder::Eq, RocOrder); +// assert_evals_to!("Num.compare 1 0", RocOrder::Gt, RocOrder); +// } + +// #[test] +// fn float_compare() { +// assert_evals_to!("Num.compare 0.01 3.14", RocOrder::Lt, RocOrder); +// assert_evals_to!("Num.compare 3.14 3.14", RocOrder::Eq, RocOrder); +// assert_evals_to!("Num.compare 3.14 0.01", RocOrder::Gt, RocOrder); +// } + +// #[test] +// fn pow() { +// assert_evals_to!("Num.pow 2.0 2.0", 4.0, f64); +// } + +// #[test] +// fn ceiling() { +// assert_evals_to!("Num.ceiling 1.1", 2, i64); +// } + +// #[test] +// fn floor() { +// assert_evals_to!("Num.floor 1.9", 1, i64); +// } + +// // #[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 +// // ); +// // } + +// #[test] +// fn max_i128() { +// assert_evals_to!( +// indoc!( +// r#" +// Num.maxI128 +// "# +// ), +// i128::MAX, +// i128 +// ); +// } + +// #[test] +// fn num_max_int() { +// assert_evals_to!( +// indoc!( +// r#" +// Num.maxInt +// "# +// ), +// i64::MAX, +// i64 +// ); +// } + +// #[test] +// fn num_min_int() { +// assert_evals_to!( +// indoc!( +// r#" +// Num.minInt +// "# +// ), +// i64::MIN, +// i64 +// ); +// } diff --git a/compiler/test_wasm/src/wasm_records.rs b/compiler/test_wasm/src/wasm_records.rs new file mode 100644 index 0000000000..8ff5bcbb69 --- /dev/null +++ b/compiler/test_wasm/src/wasm_records.rs @@ -0,0 +1,911 @@ +#![cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))] +use crate::assert_evals_to; +use indoc::indoc; + +// #[test] +// fn basic_record() { +// assert_evals_to!( +// indoc!( +// r#" +// { y: 17, x: 15, z: 19 }.x +// "# +// ), +// 15, +// i64 +// ); +// +// assert_evals_to!( +// indoc!( +// r#" +// { x: 15, y: 17, z: 19 }.y +// "# +// ), +// 17, +// i64 +// ); +// +// assert_evals_to!( +// indoc!( +// r#" +// { x: 15, y: 17, z: 19 }.z +// "# +// ), +// 19, +// i64 +// ); +// } +// +// #[test] +// fn nested_record() { +// assert_evals_to!( +// indoc!( +// r#" +// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.x +// "# +// ), +// 15, +// i64 +// ); +// +// assert_evals_to!( +// indoc!( +// r#" +// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.a +// "# +// ), +// 12, +// i64 +// ); +// +// assert_evals_to!( +// indoc!( +// r#" +// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.b +// "# +// ), +// 15, +// i64 +// ); +// +// assert_evals_to!( +// indoc!( +// r#" +// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.c +// "# +// ), +// 2, +// i64 +// ); +// +// assert_evals_to!( +// indoc!( +// r#" +// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.z +// "# +// ), +// 19, +// i64 +// ); +// } +// +// #[test] +// fn f64_record() { +// assert_evals_to!( +// indoc!( +// r#" +// rec = { y: 17.2, x: 15.1, z: 19.3 } +// +// rec.x +// "# +// ), +// 15.1, +// f64 +// ); +// +// assert_evals_to!( +// indoc!( +// r#" +// rec = { y: 17.2, x: 15.1, z: 19.3 } +// +// rec.y +// "# +// ), +// 17.2, +// f64 +// ); +// +// assert_evals_to!( +// indoc!( +// r#" +// rec = { y: 17.2, x: 15.1, z: 19.3 } +// +// rec.z +// "# +// ), +// 19.3, +// f64 +// ); +// } + +// #[test] +// fn fn_record() { +// assert_evals_to!( +// indoc!( +// r#" +// getRec = \x -> { y: 17, x, z: 19 } + +// (getRec 15).x +// "# +// ), +// 15, +// i64 +// ); + +// assert_evals_to!( +// indoc!( +// r#" +// rec = { x: 15, y: 17, z: 19 } + +// rec.y +// "# +// ), +// 17, +// i64 +// ); + +// assert_evals_to!( +// indoc!( +// r#" +// rec = { x: 15, y: 17, z: 19 } + +// rec.z +// "# +// ), +// 19, +// i64 +// ); + +// assert_evals_to!( +// indoc!( +// r#" +// rec = { x: 15, y: 17, z: 19 } + +// rec.z + rec.x +// "# +// ), +// 34, +// i64 +// ); +// } + +// #[test] +// fn def_record() { +// assert_evals_to!( +// indoc!( +// r#" +// rec = { y: 17, x: 15, z: 19 } +// +// rec.x +// "# +// ), +// 15, +// i64 +// ); +// +// assert_evals_to!( +// indoc!( +// r#" +// rec = { x: 15, y: 17, z: 19 } +// +// rec.y +// "# +// ), +// 17, +// i64 +// ); +// +// assert_evals_to!( +// indoc!( +// r#" +// rec = { x: 15, y: 17, z: 19 } +// +// rec.z +// "# +// ), +// 19, +// i64 +// ); +// } +// +// #[test] +// fn when_on_record() { +// assert_evals_to!( +// indoc!( +// r#" +// when { x: 0x2 } is +// { x } -> x + 3 +// "# +// ), +// 5, +// i64 +// ); +// } +// +// #[test] +// fn when_record_with_guard_pattern() { +// assert_evals_to!( +// indoc!( +// r#" +// when { x: 0x2, y: 3.14 } is +// { x: var } -> var + 3 +// "# +// ), +// 5, +// i64 +// ); +// } +// +// #[test] +// fn let_with_record_pattern() { +// assert_evals_to!( +// indoc!( +// r#" +// { x } = { x: 0x2, y: 3.14 } +// +// x +// "# +// ), +// 2, +// i64 +// ); +// } +// +// #[test] +// fn record_guard_pattern() { +// assert_evals_to!( +// indoc!( +// r#" +// when { x: 0x2, y: 3.14 } is +// { x: 0x4 } -> 5 +// { x } -> x + 3 +// "# +// ), +// 5, +// i64 +// ); +// } +// +// #[test] +// fn twice_record_access() { +// assert_evals_to!( +// indoc!( +// r#" +// x = {a: 0x2, b: 0x3 } +// +// x.a + x.b +// "# +// ), +// 5, +// i64 +// ); +// } +// #[test] +// fn empty_record() { +// assert_evals_to!( +// indoc!( +// r#" +// v = {} +// +// v +// "# +// ), +// (), +// () +// ); +// } + +#[test] +fn i64_record1_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3 } + "# + ), + 3, + i64 + ); +} + +#[test] +fn i64_record2_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3, y: 5 } + "# + ), + (3, 5), + (i64, i64) + ); +} + +#[test] +fn i64_record3_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3, y: 5, z: 17 } + "# + ), + (3, 5, 17), + (i64, i64, i64) + ); +} + +#[test] +fn f64_record2_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3.1, y: 5.1 } + "# + ), + (3.1, 5.1), + (f64, f64) + ); +} + +#[test] +fn f64_record3_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3.1, y: 5.1, z: 17.1 } + "# + ), + (3.1, 5.1, 17.1), + (f64, f64, f64) + ); +} + +#[test] +fn bool_record4_literal() { + assert_evals_to!( + indoc!( + r#" + record : { a : Bool, b : Bool, c : Bool, d : Bool } + record = { a: True, b: False, c : False, d : True } + + record + "# + ), + [true, false, false, true], + [bool; 4] + ); +} + +#[test] +fn i64_record9_literal() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 } + "# + ), + [3, 5, 17, 1, 9, 12, 13, 14, 15], + [i64; 9] + ); +} + +#[test] +fn bool_literal() { + assert_evals_to!( + indoc!( + r#" + x : Bool + x = True + + x + "# + ), + true, + bool + ); +} + +// #[test] +// fn optional_field_when_use_default() { +// assert_evals_to!( +// indoc!( +// r#" +// app "test" provides [ main ] to "./platform" + +// f = \r -> +// when r is +// { x: Blue, y ? 3 } -> y +// { x: Red, y ? 5 } -> y + +// main = +// a = f { x: Blue, y: 7 } +// b = f { x: Blue } +// c = f { x: Red, y: 11 } +// d = f { x: Red } + +// a * b * c * d +// "# +// ), +// 3 * 5 * 7 * 11, +// i64 +// ); +// } + +// #[test] +// fn optional_field_when_use_default_nested() { +// assert_evals_to!( +// indoc!( +// r#" +// f = \r -> +// when r is +// { x: Blue, y ? 3 } -> y +// { x: Red, y ? 5 } -> y + +// a = f { x: Blue, y: 7 } +// b = f { x: Blue } +// c = f { x: Red, y: 11 } +// d = f { x: Red } + +// a * b * c * d +// "# +// ), +// 3 * 5 * 7 * 11, +// i64 +// ); +// } + +// #[test] +// fn optional_field_when_no_use_default() { +// assert_evals_to!( +// indoc!( +// r#" +// app "test" provides [ main ] to "./platform" + +// f = \r -> +// { x ? 10, y } = r +// x + y + +// main = +// f { x: 4, y: 9 } +// "# +// ), +// 13, +// i64 +// ); +// } + +// #[test] +// fn optional_field_when_no_use_default_nested() { +// assert_evals_to!( +// indoc!( +// r#" +// f = \r -> +// { x ? 10, y } = r +// x + y + +// f { x: 4, y: 9 } +// "# +// ), +// 13, +// i64 +// ); +// } + +// #[test] +// fn optional_field_let_use_default() { +// assert_evals_to!( +// indoc!( +// r#" +// app "test" provides [ main ] to "./platform" + +// f = \r -> +// { x ? 10, y } = r +// x + y + +// main = +// f { y: 9 } +// "# +// ), +// 19, +// i64 +// ); +// } + +// #[test] +// fn optional_field_let_no_use_default() { +// assert_evals_to!( +// indoc!( +// r#" +// app "test" provides [ main ] to "./platform" + +// f = \r -> +// { x ? 10, y } = r +// x + y + +// main = +// f { x: 4, y: 9 } +// "# +// ), +// 13, +// i64 +// ); +// } + +// #[test] +// fn optional_field_let_no_use_default_nested() { +// assert_evals_to!( +// indoc!( +// r#" +// f = \r -> +// { x ? 10, y } = r +// x + y + +// f { x: 4, y: 9 } +// "# +// ), +// 13, +// i64 +// ); +// } + +// #[test] +// fn optional_field_function_use_default() { +// assert_evals_to!( +// indoc!( +// r#" +// f = \{ x ? 10, y } -> x + y + +// f { y: 9 } +// "# +// ), +// 19, +// i64 +// ); +// } + +// #[test] +// #[ignore] +// fn optional_field_function_no_use_default() { +// // blocked on https://github.com/rtfeldman/roc/issues/786 +// assert_evals_to!( +// indoc!( +// r#" +// app "test" provides [ main ] to "./platform" + +// f = \{ x ? 10, y } -> x + y + +// main = +// f { x: 4, y: 9 } +// "# +// ), +// 13, +// i64 +// ); +// } + +// #[test] +// #[ignore] +// fn optional_field_function_no_use_default_nested() { +// // blocked on https://github.com/rtfeldman/roc/issues/786 +// assert_evals_to!( +// indoc!( +// r#" +// f = \{ x ? 10, y } -> x + y + +// f { x: 4, y: 9 } +// "# +// ), +// 13, +// i64 +// ); +// } + +// #[test] +// fn optional_field_singleton_record() { +// assert_evals_to!( +// indoc!( +// r#" +// when { x : 4 } is +// { x ? 3 } -> x +// "# +// ), +// 4, +// i64 +// ); +// } + +// #[test] +// fn optional_field_empty_record() { +// assert_evals_to!( +// indoc!( +// r#" +// when { } is +// { x ? 3 } -> x +// "# +// ), +// 3, +// i64 +// ); +// } + +#[test] +fn return_record_3() { + assert_evals_to!( + indoc!( + r#" + { x: 3, y: 5, z: 4 } + "# + ), + (3, 5, 4), + (i64, i64, i64) + ); +} + +#[test] +fn return_record_4() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 4, d: 2 } + "# + ), + [3, 5, 4, 2], + [i64; 4] + ); +} + +#[test] +fn return_record_5() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 4, d: 2, e: 1 } + "# + ), + [3, 5, 4, 2, 1], + [i64; 5] + ); +} + +#[test] +fn return_record_6() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 } + "# + ), + [3, 5, 4, 2, 1, 7], + [i64; 6] + ); +} + +#[test] +fn return_record_7() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 } + "# + ), + [3, 5, 4, 2, 1, 7, 8], + [i64; 7] + ); +} + +#[test] +fn return_record_float_int() { + assert_evals_to!( + indoc!( + r#" + { a: 3.14, b: 0x1 } + "# + ), + (3.14, 0x1), + (f64, i64) + ); +} + +#[test] +fn return_record_int_float() { + assert_evals_to!( + indoc!( + r#" + { a: 0x1, b: 3.14 } + "# + ), + (0x1, 3.14), + (i64, f64) + ); +} + +#[test] +fn return_record_float_float() { + assert_evals_to!( + indoc!( + r#" + { a: 6.28, b: 3.14 } + "# + ), + (6.28, 3.14), + (f64, f64) + ); +} + +#[test] +fn return_record_float_float_float() { + assert_evals_to!( + indoc!( + r#" + { a: 6.28, b: 3.14, c: 0.1 } + "# + ), + (6.28, 3.14, 0.1), + (f64, f64, f64) + ); +} + +// #[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 accessor() { +// assert_evals_to!( +// indoc!( +// r#" +// .foo { foo: 4 } + .foo { bar: 6.28, foo: 3 } +// "# +// ), +// 7, +// i64 +// ); +// } + +// #[test] +// fn accessor_single_element_record() { +// assert_evals_to!( +// indoc!( +// r#" +// .foo { foo: 4 } +// "# +// ), +// 4, +// i64 +// ); +// } + +// #[test] +// fn update_record() { +// assert_evals_to!( +// indoc!( +// r#" +// rec = { foo: 42, bar: 6 } + +// { rec & foo: rec.foo + 1 } +// "# +// ), +// (6, 43), +// (i64, i64) +// ); +// } + +// #[test] +// fn update_single_element_record() { +// assert_evals_to!( +// indoc!( +// r#" +// rec = { foo: 42} + +// { rec & foo: rec.foo + 1 } +// "# +// ), +// 43, +// i64 +// ); +// } + +// #[test] +// fn booleans_in_record() { +// assert_evals_to!( +// indoc!("{ x: 1 == 1, y: 1 == 1 }"), +// (true, true), +// (bool, bool) +// ); +// assert_evals_to!( +// indoc!("{ x: 1 != 1, y: 1 == 1 }"), +// (false, true), +// (bool, bool) +// ); +// assert_evals_to!( +// indoc!("{ x: 1 == 1, y: 1 != 1 }"), +// (true, false), +// (bool, bool) +// ); +// assert_evals_to!( +// indoc!("{ x: 1 != 1, y: 1 != 1 }"), +// (false, false), +// (bool, bool) +// ); +// } + +// #[test] +// fn alignment_in_record() { +// assert_evals_to!( +// indoc!("{ c: 32, b: if True then Red else if True then Green else Blue, a: 1 == 1 }"), +// (32i64, true, 2u8), +// (i64, bool, u8) +// ); +// } + +#[test] +fn stack_memory_return_from_branch() { + // stack memory pointer should end up in the right place after returning from a branch + assert_evals_to!( + indoc!( + r#" + stackMemoryJunk = { x: 999, y: 111 } + if True then + { x: 123, y: 321 } + else + stackMemoryJunk + "# + ), + (123, 321), + (i64, i64) + ); +} + +// #[test] +// fn blue_and_present() { +// assert_evals_to!( +// indoc!( +// r#" +// f = \r -> +// when r is +// { x: Blue, y ? 3 } -> y +// { x: Red, y ? 5 } -> y + +// f { x: Blue, y: 7 } +// "# +// ), +// 7, +// i64 +// ); +// } + +// #[test] +// fn blue_and_absent() { +// assert_evals_to!( +// indoc!( +// r#" +// f = \r -> +// when r is +// { x: Blue, y ? 3 } -> y +// { x: Red, y ? 5 } -> y + +// f { x: Blue } +// "# +// ), +// 3, +// i64 +// ); +// } diff --git a/compiler/types/Cargo.toml b/compiler/types/Cargo.toml index 512437bd66..2d42f75884 100644 --- a/compiler/types/Cargo.toml +++ b/compiler/types/Cargo.toml @@ -14,7 +14,6 @@ static_assertions = "1.1.0" [dev-dependencies] pretty_assertions = "0.5.1" -maplit = "1.0.1" indoc = "0.3.3" quickcheck = "0.8" quickcheck_macros = "0.8" diff --git a/compiler/unify/Cargo.toml b/compiler/unify/Cargo.toml index 73d85a6f36..06fa81b371 100644 --- a/compiler/unify/Cargo.toml +++ b/compiler/unify/Cargo.toml @@ -12,7 +12,6 @@ roc_types = { path = "../types" } [dev-dependencies] pretty_assertions = "0.5.1" -maplit = "1.0.1" indoc = "0.3.3" quickcheck = "0.8" quickcheck_macros = "0.8" diff --git a/docs/Cargo.toml b/docs/Cargo.toml index fc0b9874d8..727cb3d52e 100644 --- a/docs/Cargo.toml +++ b/docs/Cargo.toml @@ -14,7 +14,6 @@ roc_load = { path = "../compiler/load" } roc_builtins = { path = "../compiler/builtins" } roc_can = { path = "../compiler/can" } roc_code_markup = { path = "../code_markup"} -roc_fmt = { path = "../compiler/fmt" } roc_module = { path = "../compiler/module" } roc_region = { path = "../compiler/region" } roc_types = { path = "../compiler/types" } @@ -25,6 +24,5 @@ snafu = { version = "0.6", features = ["backtraces"] } [dev-dependencies] pretty_assertions = "0.5.1" -maplit = "1.0.1" tempfile = "3.2.0" uuid = { version = "0.8", features = ["v4"] } diff --git a/editor/Cargo.toml b/editor/Cargo.toml index c32c9372f7..83f5d387d2 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -6,6 +6,12 @@ license = "UPL-1.0" edition = "2018" description = "An editor for Roc" +[package.metadata.cargo-udeps.ignore] +# confy is currently unused but should not be removed +normal = ["confy"] +#development = [] +#build = [] + [dependencies] roc_ast = { path = "../ast" } roc_collections = { path = "../compiler/collections" } @@ -19,7 +25,6 @@ roc_module = { path = "../compiler/module" } roc_problem = { path = "../compiler/problem" } roc_types = { path = "../compiler/types" } roc_unify = { path = "../compiler/unify" } -roc_fmt = { path = "../compiler/fmt" } roc_reporting = { path = "../compiler/reporting" } roc_solve = { path = "../compiler/solve" } ven_graph = { path = "../vendor/pathfinding" } @@ -33,7 +38,6 @@ winit = "0.24" wgpu = "0.10" glyph_brush = "0.7" log = "0.4" -zerocopy = "0.3" env_logger = "0.8" futures = "0.3" wgpu_glyph = "0.14" @@ -42,18 +46,13 @@ snafu = { version = "0.6", features = ["backtraces"] } colored = "2" pest = "2.1" pest_derive = "2.1" -ropey = "1.2.0" copypasta = "0.7.1" -indoc = "1.0" palette = "0.5" -# confy is currently unused but should not be removed confy = { git = 'https://github.com/rust-cli/confy', features = [ "yaml_conf" ], default-features = false } serde = { version = "1.0.123", features = ["derive"] } nonempty = "0.6.0" -tempfile = "3.2.0" -uuid = { version = "0.8", features = ["v4"] } fs_extra = "1.2.0" rodio = "0.14.0" threadpool = "1.8.1" @@ -64,7 +63,9 @@ features = ["derive"] [dev-dependencies] pretty_assertions = "0.6" -maplit = "1.0.1" quickcheck = "1.0" quickcheck_macros = "1.0" rand = "0.8.2" +indoc = "1.0" +tempfile = "3.2.0" +uuid = { version = "0.8", features = ["v4"] } diff --git a/editor/src/editor/main.rs b/editor/src/editor/main.rs index 8ecded6f02..d1b1431afa 100644 --- a/editor/src/editor/main.rs +++ b/editor/src/editor/main.rs @@ -79,7 +79,9 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box *mut c_void { +pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { libc::malloc(size) } #[no_mangle] -pub unsafe fn roc_realloc( +pub unsafe extern "C" fn roc_realloc( c_ptr: *mut c_void, new_size: usize, _old_size: usize, @@ -45,12 +45,12 @@ pub unsafe fn roc_realloc( } #[no_mangle] -pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { +pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { libc::free(c_ptr) } #[no_mangle] -pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { +pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { match tag_id { 0 => { let slice = CStr::from_ptr(c_ptr as *const c_char); @@ -73,7 +73,7 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut } #[no_mangle] -pub fn rust_main() -> i32 { +pub extern "C" fn rust_main() -> i32 { let arg = env::args().skip(1).next().unwrap(); let arg = RocStr::from_slice(arg.as_bytes()); @@ -115,7 +115,7 @@ unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 { } #[no_mangle] -pub fn roc_fx_getLine() -> RocStr { +pub extern "C" fn roc_fx_getLine() -> RocStr { use std::io::{self, BufRead}; let stdin = io::stdin(); @@ -125,7 +125,7 @@ pub fn roc_fx_getLine() -> RocStr { } #[no_mangle] -pub fn roc_fx_getChar() -> u8 { +pub extern "C" fn roc_fx_getChar() -> u8 { use std::io::{self, BufRead}; let mut buffer = [0]; @@ -141,7 +141,7 @@ pub fn roc_fx_getChar() -> u8 { } #[no_mangle] -pub fn roc_fx_putLine(line: RocStr) -> () { +pub extern "C" fn roc_fx_putLine(line: RocStr) -> () { let bytes = line.as_slice(); let string = unsafe { std::str::from_utf8_unchecked(bytes) }; println!("{}", string); @@ -154,7 +154,7 @@ pub fn roc_fx_putLine(line: RocStr) -> () { } #[no_mangle] -pub fn roc_fx_putRaw(line: RocStr) -> () { +pub extern "C" fn roc_fx_putRaw(line: RocStr) -> () { let bytes = line.as_slice(); let string = unsafe { std::str::from_utf8_unchecked(bytes) }; print!("{}", string); @@ -167,7 +167,7 @@ pub fn roc_fx_putRaw(line: RocStr) -> () { } #[no_mangle] -pub fn roc_fx_getFileLine(br_ptr: *mut BufReader) -> RocStr { +pub extern "C" fn roc_fx_getFileLine(br_ptr: *mut BufReader) -> RocStr { let br = unsafe { &mut *br_ptr }; let mut line1 = String::default(); @@ -178,7 +178,7 @@ pub fn roc_fx_getFileLine(br_ptr: *mut BufReader) -> RocStr { } #[no_mangle] -pub fn roc_fx_getFileBytes(br_ptr: *mut BufReader) -> RocList { +pub extern "C" fn roc_fx_getFileBytes(br_ptr: *mut BufReader) -> RocList { let br = unsafe { &mut *br_ptr }; let mut buffer = [0; 0x10 /* This is intentially small to ensure correct implementation */]; @@ -190,22 +190,25 @@ pub fn roc_fx_getFileBytes(br_ptr: *mut BufReader) -> RocList { } #[no_mangle] -pub fn roc_fx_closeFile(br_ptr: *mut BufReader) -> () { +pub extern "C" fn roc_fx_closeFile(br_ptr: *mut BufReader) -> () { unsafe { Box::from_raw(br_ptr); } } #[no_mangle] -pub fn roc_fx_openFile(name: RocStr) -> *mut BufReader { +pub extern "C" fn roc_fx_openFile(name: RocStr) -> *mut BufReader { let f = File::open(name.as_str()).expect("Unable to open file"); let br = BufReader::new(f); + // don't mess with the refcount! + core::mem::forget(name); + Box::into_raw(Box::new(br)) } #[no_mangle] -pub fn roc_fx_withFileOpen(name: RocStr, buffer: *const u8) -> () { +pub extern "C" fn roc_fx_withFileOpen(name: RocStr, buffer: *const u8) -> () { // let f = File::open(name.as_str()).expect("Unable to open file"); // let mut br = BufReader::new(f); diff --git a/examples/hello-rust/platform/src/lib.rs b/examples/hello-rust/platform/src/lib.rs index 341556bb4b..6b054aa551 100644 --- a/examples/hello-rust/platform/src/lib.rs +++ b/examples/hello-rust/platform/src/lib.rs @@ -1,9 +1,9 @@ #![allow(non_snake_case)] use core::ffi::c_void; -use libc::c_char; use roc_std::RocStr; use std::ffi::CStr; +use std::os::raw::c_char; extern "C" { #[link_name = "roc__mainForHost_1_exposed"] diff --git a/nix/zig.nix b/nix/zig.nix new file mode 100644 index 0000000000..55fd1680e8 --- /dev/null +++ b/nix/zig.nix @@ -0,0 +1,41 @@ +{ pkgs }: + +let + version = "0.8.0"; + + osName = if pkgs.stdenv.isDarwin then "macos" else "linux"; + + splitSystem = builtins.split "-" builtins.currentSystem; + arch = builtins.elemAt splitSystem 0; + isAarch64 = arch == "aarch64"; + + archiveName = "zig-${osName}-${arch}-${version}"; + + # If your system is not aarch64, we assume it's x86_64 + sha256 = if pkgs.stdenv.isDarwin then + if isAarch64 then + "b32d13f66d0e1ff740b3326d66a469ee6baddbd7211fa111c066d3bd57683111" + else + "279f9360b5cb23103f0395dc4d3d0d30626e699b1b4be55e98fd985b62bc6fbe" + else if isAarch64 then + "ee204ca2c2037952cf3f8b10c609373a08a291efa4af7b3c73be0f2b27720470" + else + "502625d3da3ae595c5f44a809a87714320b7a40e6dff4a895b5fa7df3391d01e"; +in pkgs.stdenv.mkDerivation { + pname = "zig"; + version = version; + src = pkgs.fetchurl { + inherit sha256; + name = "${archiveName}.tar.xz"; + url = "https://ziglang.org/download/${version}/${archiveName}.tar.xz"; + }; + phases = [ "unpackPhase" ]; + unpackPhase = '' + mkdir -p $out/bin + tar -xf $src + cp ${archiveName}/zig $out/zig + cp -r ${archiveName}/lib $out/lib + ln -s "$out/zig" "$out/bin/zig" + chmod +x $out/bin/zig + ''; +} diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index b6f07dcb8a..28ba317ab0 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -136,6 +136,7 @@ impl RocList { where T: Clone, { + assert!(capacity > 0); assert!(slice.len() <= capacity); let ptr = slice.as_ptr(); @@ -197,7 +198,12 @@ impl RocList { where T: Clone, { - Self::from_slice_with_capacity(slice, slice.len()) + // Avoid allocation with empty list. + if slice.is_empty() { + Self::default() + } else { + Self::from_slice_with_capacity(slice, slice.len()) + } } pub fn as_slice(&self) -> &[T] { diff --git a/shell.nix b/shell.nix index 204f3836bd..3df6678197 100644 --- a/shell.nix +++ b/shell.nix @@ -32,6 +32,7 @@ let llvmPkgs = pkgs.llvmPackages_12; + zig = import ./nix/zig.nix { inherit pkgs; }; debugir = import ./nix/debugir.nix { inherit pkgs; }; inputs = with pkgs; [ @@ -70,9 +71,39 @@ in pkgs.mkShell { # Additional Env vars LLVM_SYS_120_PREFIX = "${llvmPkgs.llvm.dev}"; + NIX_GLIBC_PATH = + if pkgs.stdenv.isLinux then "${pkgs.glibc_multi.out}/lib" else ""; LD_LIBRARY_PATH = with pkgs; lib.makeLibraryPath ([ pkg-config stdenv.cc.cc.lib libffi ncurses zlib ] ++ linuxInputs); - NIX_GLIBC_PATH = - if pkgs.stdenv.isLinux then "${pkgs.glibc_multi.out}/lib" else ""; + + COREAUDIO_SDK_PATH = if pkgs.stdenv.isDarwin then + # The coreaudio-sys crate is configured to look for things in whatever the + # output of `xcrun --sdk macosx --show-sdk-path` is. However, this does not + # always contain the right frameworks, and it uses system versions instead of + # what we control via Nix. Instead of having to run a lot of extra scripts + # to set our systems up to build, we can just create a SDK directory with + # the same layout as the `MacOSX{version}.sdk` that XCode produces. + pkgs.symlinkJoin { + name = "sdk"; + paths = with pkgs.darwin.apple_sdk.frameworks; [ + AudioToolbox + AudioUnit + CoreAudio + CoreFoundation + CoreMIDI + OpenAL + ]; + postBuild = '' + mkdir $out/System + mv $out/Library $out/System + ''; + } + else + # TODO: I'm not 100% confident that this being blank won't cause issues for + # Nix-on-Linux development. It may be sufficient to use the pkgs.symlinkJoin + # above regardless of system! That'd set us up for cross-compilation as well. + ""; + + }