diff --git a/.envrc b/.envrc deleted file mode 100644 index 1d953f4bd7..0000000000 --- a/.envrc +++ /dev/null @@ -1 +0,0 @@ -use nix diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 9f3791e4ee..a7977235f7 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,32 +1,31 @@ on: schedule: - - cron: '0 0 * * *' + - cron: '0 9 * * *' name: Nightly Release Build jobs: build: name: Test and Build - runs-on: ubuntu-latest + runs-on: [self-hosted, i5-4690K] + timeout-minutes: 90 + env: + FORCE_COLOR: 1 # for earthly logging steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - name: Earthly print version + run: earthly --version + - name: install dependencies, build, run tests, build release + run: ./ci/safe-earthly.sh +build-nightly-release + - name: Create pre-release with test_archive.tar.gz + uses: WebFreak001/deploy-nightly@v1.1.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions with: - profile: minimal - toolchain: stable - override: true - - run: rustup component add rustfmt - - uses: actions-rs/cargo@v1 - name: cargo update (needed for Inkwell) - with: - command: update - - uses: actions-rs/cargo@v1 - name: cargo test - with: - command: test - args: --release - - uses: actions-rs/cargo@v1 - name: cargo build - with: - command: build - args: --release + upload_url: https://uploads.github.com/repos/rtfeldman/roc/releases/51880579/assets{?name,label} + release_id: 51880579 + asset_path: ./roc_linux_x86_64.tar.gz + asset_name: roc_nightly-linux_x86_64-$$.tar.gz # $$ to inserts date (YYYYMMDD) and 6 letter commit hash + asset_content_type: application/gzip + max_releases: 3 + diff --git a/.gitignore b/.gitignore index 6ad9f80e0a..240df20129 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ target generated-docs zig-cache .direnv +.envrc *.rs.bk *.o *.tmp @@ -38,3 +39,6 @@ bench-folder* # earthly earthly_log.txt + +# created to test release +roc_linux_x86_64.tar.gz diff --git a/AUTHORS b/AUTHORS index cd39a49efc..ceb746617b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -37,3 +37,10 @@ Kofi Gumbs Luiz de Oliveira Chelsea Troy Shritesh Bhattarai +Kevin Sjöberg +Viktor Fröberg +Locria Cyber +Matthias Beyer +Tim Whiting +Logan Lowder +Joshua Warner diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index a531e22169..35df489a83 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -50,8 +50,12 @@ If you want to install it manually, you can also download Zig directly [here](ht **version: 12.0.x** For macOS, you can install LLVM 12 using `brew install llvm@12` and then adding -`/usr/local/opt/llvm/bin` to your `PATH`. You can confirm this worked by +`/usr/local/opt/llvm@12/bin` to your `PATH`. You can confirm this worked by running `llc --version` - it should mention "LLVM version 12.0.0" at the top. +You may also need to manually specify a prefix env var like so: +``` +export LLVM_SYS_120_PREFIX=/usr/local/opt/llvm@12 +``` For Ubuntu and Debian: ``` @@ -62,7 +66,7 @@ chmod +x llvm.sh ``` If you use this script, you'll need to add `clang` and `llvm-as` to your `PATH`. -By default, the script installs them as `llvm-as-12` and `clang-12`, +By default, the script installs them as `clang-12` and `llvm-as-12`, respectively. You can address this with symlinks like so: ``` @@ -78,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 @@ -90,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` @@ -100,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` @@ -116,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 @@ -193,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 325263d15e..aaad62075a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -457,14 +457,16 @@ dependencies = [ [[package]] name = "clap" version = "3.0.0-beta.1" -source = "git+https://github.com/rtfeldman/clap?branch=master#e1d83a78804a271b053d4d21f69b67f7eb01ad01" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "860643c53f980f0d38a5e25dfab6c3c93b2cb3aa1fe192643d17a293c6c41936" dependencies = [ "atty", "bitflags", "clap_derive", "indexmap", "lazy_static", - "strsim 0.9.3", + "os_str_bytes", + "strsim 0.10.0", "termcolor", "textwrap", "unicode-width", @@ -473,11 +475,12 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.0.0-beta.1" -source = "git+https://github.com/rtfeldman/clap?branch=master#e1d83a78804a271b053d4d21f69b67f7eb01ad01" +version = "3.0.0-beta.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b15c6b4f786ffb6192ffe65a36855bc1fc2444bcd0945ae16748dcd6ed7d0d3" dependencies = [ "heck", - "proc-macro-error 0.4.12", + "proc-macro-error", "proc-macro2 1.0.29", "quote 1.0.9", "syn 1.0.76", @@ -495,7 +498,6 @@ version = "0.1.0" dependencies = [ "bumpalo", "criterion", - "inlinable_string", "rlimit", "roc_cli", "roc_collections", @@ -1848,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" @@ -2711,6 +2707,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "os_str_bytes" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85" + [[package]] name = "output_vt100" version = "0.1.2" @@ -2772,12 +2774,6 @@ dependencies = [ "syn 1.0.76", ] -[[package]] -name = "parity-wasm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" - [[package]] name = "parking_lot" version = "0.11.2" @@ -3017,45 +3013,19 @@ dependencies = [ "toml", ] -[[package]] -name = "proc-macro-error" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" -dependencies = [ - "proc-macro-error-attr 0.4.12", - "proc-macro2 1.0.29", - "quote 1.0.9", - "syn 1.0.76", - "version_check", -] - [[package]] name = "proc-macro-error" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ - "proc-macro-error-attr 1.0.4", + "proc-macro-error-attr", "proc-macro2 1.0.29", "quote 1.0.9", "syn 1.0.76", "version_check", ] -[[package]] -name = "proc-macro-error-attr" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" -dependencies = [ - "proc-macro2 1.0.29", - "quote 1.0.9", - "syn 1.0.76", - "syn-mid", - "version_check", -] - [[package]] name = "proc-macro-error-attr" version = "1.0.4" @@ -3597,9 +3567,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", @@ -3631,7 +3599,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", @@ -3649,7 +3616,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", @@ -3676,9 +3642,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", @@ -3717,6 +3681,7 @@ name = "roc_code_markup" version = "0.1.0" dependencies = [ "bumpalo", + "itertools 0.10.1", "palette", "roc_ast", "roc_module", @@ -3741,7 +3706,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", @@ -3759,7 +3723,6 @@ name = "roc_docs" version = "0.1.0" dependencies = [ "bumpalo", - "maplit", "pretty_assertions 0.5.1", "pulldown-cmark", "roc_ast", @@ -3767,12 +3730,12 @@ dependencies = [ "roc_can", "roc_code_markup", "roc_collections", - "roc_fmt", "roc_load", "roc_module", "roc_parse", "roc_region", "roc_types", + "snafu", "tempfile", "uuid", ] @@ -3797,7 +3760,6 @@ dependencies = [ "indoc 1.0.3", "libc", "log", - "maplit", "nonempty", "page_size", "palette", @@ -3812,7 +3774,6 @@ dependencies = [ "roc_can", "roc_code_markup", "roc_collections", - "roc_fmt", "roc_load", "roc_module", "roc_parse", @@ -3823,7 +3784,6 @@ dependencies = [ "roc_types", "roc_unify", "rodio", - "ropey", "serde", "snafu", "tempfile", @@ -3833,7 +3793,6 @@ dependencies = [ "wgpu", "wgpu_glyph", "winit", - "zerocopy", ] [[package]] @@ -3844,7 +3803,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", @@ -3863,9 +3821,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", @@ -3900,8 +3856,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", @@ -3931,8 +3885,6 @@ version = "0.1.0" dependencies = [ "bumpalo", "indoc 0.3.6", - "libc", - "parity-wasm", "pretty_assertions 0.5.1", "roc_builtins", "roc_can", @@ -4008,11 +3960,11 @@ dependencies = [ "bumpalo", "indoc 0.3.6", "lazy_static", - "maplit", "pretty_assertions 0.5.1", "roc_collections", "roc_ident", "roc_region", + "snafu", "static_assertions", ] @@ -4024,7 +3976,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", @@ -4066,7 +4017,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", @@ -4089,7 +4039,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", @@ -4113,7 +4062,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", @@ -4140,7 +4088,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", @@ -4156,7 +4103,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", @@ -4185,15 +4131,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" @@ -4639,29 +4576,6 @@ dependencies = [ "unicode-xid 0.2.2", ] -[[package]] -name = "syn-mid" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baa8e7560a164edb1621a55d18a0c59abf49d360f47aa7b821061dd7eea7fac9" -dependencies = [ - "proc-macro2 1.0.29", - "quote 1.0.9", - "syn 1.0.76", -] - -[[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" @@ -4703,7 +4617,6 @@ dependencies = [ "inkwell 0.1.0", "libc", "libloading 0.6.7", - "maplit", "quickcheck 0.8.5", "quickcheck_macros 0.8.0", "roc_build", @@ -4736,31 +4649,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", ] @@ -4768,12 +4666,33 @@ 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", + "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" @@ -5256,7 +5175,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ee7b351bcc1e782997c72dc0b5b328f3ddcad4813b8ce3cac3f25ae5a4ab56b" dependencies = [ - "proc-macro-error 1.0.4", + "proc-macro-error", "proc-macro2 1.0.29", "quote 1.0.9", "syn 1.0.76", @@ -5775,24 +5694,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/CodeOfConduct.md b/CodeOfConduct.md index aa99bba432..05bfdf467d 100644 --- a/CodeOfConduct.md +++ b/CodeOfConduct.md @@ -34,8 +34,8 @@ In the Roc community we strive to go the extra step to look out for each other. And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could've communicated better — remember that it's your responsibility to make your fellow Roc programmers comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust. -The enforcement policies listed above apply to all official Roc venues; including official IRC channels (#rust, #rust-internals, #rust-tools, #rust-libs, #rustc, #rust-beginners, #rust-docs, #rust-community, #rust-lang, and #cargo); GitHub repositories under rust-lang, rust-lang-nursery, and rust-lang-deprecated; and all forums under rust-lang.org (users.rust-lang.org, internals.rust-lang.org). For other projects adopting the Roc Code of Conduct, please contact the maintainers of those projects for enforcement. If you wish to use this code of conduct for your own project, consider explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion. +The enforcement policies listed above apply to all official Roc venues; including official Zulip chat (https://roc.zulipchat.com); and GitHub repositories under the roc-lang organization. If you wish to use this code of conduct (or the Rust code of conduct, on which it is based) for your own project, consider explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion. *Adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the [Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/).* -[mod_team]: https://www.rust-lang.org/team.html#Moderation-team +[mod_team]: https://www.roc-lang.org/moderation diff --git a/Earthfile b/Earthfile index 815b328641..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: @@ -35,7 +35,7 @@ install-zig-llvm-valgrind-clippy-rustfmt: RUN rustup component add rustfmt # criterion RUN cargo install cargo-criterion - # wasm + # editor RUN apt -y install libxkbcommon-dev # sccache RUN apt -y install libssl-dev @@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt: copy-dirs: FROM +install-zig-llvm-valgrind-clippy-rustfmt - COPY --dir cli compiler docs editor ast code_markup utils roc_std vendor examples linker Cargo.toml Cargo.lock ./ + COPY --dir cli compiler docs editor ast code_markup utils roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./ test-zig: FROM +install-zig-llvm-valgrind-clippy-rustfmt @@ -67,12 +67,14 @@ check-rustfmt: check-typos: RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically - COPY --dir .github ci cli compiler docs editor examples ast code_markup utils linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./ + COPY --dir .github ci cli compiler docs editor examples ast code_markup utils linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix version.txt ./ RUN typos test-rust: FROM +copy-dirs ENV RUST_BACKTRACE=1 + # for race condition problem with cli test + ENV ROC_NUM_WORKERS=1 # run one of the benchmarks to make sure the host is compiled # not pre-compiling the host can cause race conditions RUN echo "4" | cargo run --release examples/benchmarks/NQueens.roc @@ -101,6 +103,16 @@ test-all: BUILD +test-rust BUILD +verify-no-git-changes +build-nightly-release: + FROM +test-rust + COPY --dir .git ./ + # version.txt is used by the CLI: roc --version + RUN printf "nightly pre-release, built from commit " > version.txt + RUN git log --pretty=format:'%h' -n 1 >> version.txt + RUN cargo build --release + RUN cd ./target/release && tar -czvf roc_linux_x86_64.tar.gz ./roc + SAVE ARTIFACT ./target/release/roc_linux_x86_64.tar.gz AS LOCAL roc_linux_x86_64.tar.gz + # compile everything needed for benchmarks and output a self-contained dir from which benchmarks can be run. prep-bench-folder: FROM +copy-dirs diff --git a/README.md b/README.md index 49400318fb..fc520753a0 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ If you're curious about where the language's name and logo came from, ## State of Roc Roc is not ready for production yet. You are likely to encounter bugs. Publishing packages or documentation is not yet supported. -Many programs can however be compiled correctly. Check out [examples](examples) and [examples/benchmarks](examples/benchmarks). There are minimal platforms for Rust, Zig, C and an HTTP server. We are hard at work to make programming in Roc a delightful experience! +Many programs can however be compiled correctly. Check out [examples](examples) and [examples/benchmarks](examples/benchmarks). There are minimal platforms for Rust, Zig, C, Swift and an HTTP server. We are hard at work to make programming in Roc a delightful experience! ## Getting started @@ -64,7 +64,7 @@ By using systems-level programming languages like C and C++, platform authors sa Roc is designed to make the "systems-level platform, higher-level application" experience as nice as possible. * **Application** authors code exclusively in Roc. It's a language designed for nice ergonomics. The syntax resembles Ruby or CoffeeScript, and it has a fast compiler with full type inference. -* **Platform** authors code almost exclusively in a systems-level language like C, C++, Rust, or [Zig](https://ziglang.org/), except for the thin Roc API they expose to application authors. Roc application code compiles to machine code, and production builds of Roc apps benefit from the same [LLVM](https://llvm.org/) optimizations that C++, Rust, and Zig do. Roc application authors do not need to know this lower-level code exists; all they have to interact with is the platform's API, which is exposed as an ordinary Roc API. +* **Platform** authors code almost exclusively in a systems-level language like C, C++, Rust, Swift or [Zig](https://ziglang.org/), except for the thin Roc API they expose to application authors. Roc application code compiles to machine code, and production builds of Roc apps benefit from the same [LLVM](https://llvm.org/) optimizations that C++, Rust, Swift and Zig do. Roc application authors do not need to know this lower-level code exists; all they have to interact with is the platform's API, which is exposed as an ordinary Roc API. Every Roc application is built on top of exactly one Roc platform. There is no such thing as a Roc application that runs without a platform, and there is no default platform. You must choose one! 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/ast_error.rs b/ast/src/ast_error.rs index 6fb8546212..b394566633 100644 --- a/ast/src/ast_error.rs +++ b/ast/src/ast_error.rs @@ -1,3 +1,6 @@ +use roc_module::{ident::Ident, module_err::ModuleError}; +use roc_parse::parser::SyntaxError; +use roc_region::all::{Located, Region}; use snafu::{Backtrace, Snafu}; use crate::lang::core::ast::ASTNodeId; @@ -33,6 +36,38 @@ pub enum ASTError { encountered_pattern2: String, backtrace: Backtrace, }, + #[snafu(display("IdentExistsError: {}", msg))] + IdentExistsError { msg: String }, + + WrapModuleError { + #[snafu(backtrace)] + source: ModuleError, + }, + + #[snafu(display("SyntaxError: {}", msg))] + SyntaxErrorNoBacktrace { msg: String }, } pub type ASTResult = std::result::Result; + +impl From for ASTError { + fn from(module_err: ModuleError) -> Self { + Self::WrapModuleError { source: module_err } + } +} + +impl From<(Region, Located)> for ASTError { + fn from(ident_exists_err: (Region, Located)) -> Self { + Self::IdentExistsError { + msg: format!("{:?}", ident_exists_err), + } + } +} + +impl<'a> From> for ASTError { + fn from(syntax_err: SyntaxError) -> Self { + Self::SyntaxErrorNoBacktrace { + msg: format!("{:?}", syntax_err), + } + } +} diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index 1454f1d936..013c505705 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -273,7 +273,7 @@ pub fn constrain_expr<'a>( Expr2::Call { args, expr_var, - expr: expr_node_id, + expr_id: expr_node_id, closure_var, fn_var, .. @@ -944,8 +944,8 @@ pub fn constrain_expr<'a>( } Expr2::Closure { args, - name, - body: body_id, + uniq_symbol, + body_id, function_type: fn_var, extra, .. @@ -992,7 +992,7 @@ pub fn constrain_expr<'a>( let closure_constraint = constrain_closure_size( arena, env, - *name, + *uniq_symbol, region, captured_symbols, *closure_var, diff --git a/ast/src/lang/core/def/def.rs b/ast/src/lang/core/def/def.rs index 1fd54b4828..dca2a20644 100644 --- a/ast/src/lang/core/def/def.rs +++ b/ast/src/lang/core/def/def.rs @@ -488,9 +488,9 @@ fn canonicalize_pending_def<'a>( match loc_can_expr { Expr2::Closure { args: closure_args, - body, + body_id, extra, - name: closure_symbol, + uniq_symbol: closure_symbol, .. } => { let symbol = match env.pool[loc_can_pattern] { @@ -570,7 +570,7 @@ fn canonicalize_pending_def<'a>( arguments, rigids: env.pool.add(rigids), return_type, - body, + body_id, }; let def = Def::Function(function_def); @@ -656,9 +656,9 @@ fn canonicalize_pending_def<'a>( match loc_can_expr { Expr2::Closure { args: closure_args, - body, + body_id, extra, - name: closure_symbol, + uniq_symbol: closure_symbol, .. } => { let symbol = match env.pool[loc_can_pattern] { @@ -703,7 +703,7 @@ fn canonicalize_pending_def<'a>( name: symbol, arguments, return_var: env.var_store.fresh(), - body, + body_id, }; let def = Def::Function(function_def); diff --git a/ast/src/lang/core/def/def2.rs b/ast/src/lang/core/def/def2.rs index 023a39760d..0717ead70b 100644 --- a/ast/src/lang/core/def/def2.rs +++ b/ast/src/lang/core/def/def2.rs @@ -1,8 +1,7 @@ +use roc_module::symbol::IdentId; + use crate::{ - lang::core::{ - expr::{expr2::Expr2, expr2_to_string::expr2_to_string}, - pattern::Pattern2, - }, + lang::core::expr::{expr2::Expr2, expr2_to_string::expr2_to_string}, mem_pool::pool::{NodeId, Pool}, }; @@ -11,7 +10,7 @@ use crate::{ pub enum Def2 { // ValueDef example: `main = "Hello, world!"`. identifier -> `main`, expr -> "Hello, world!" ValueDef { - identifier_id: NodeId, + identifier_id: IdentId, expr_id: NodeId, }, Blank, @@ -30,7 +29,7 @@ pub fn def2_to_string(node_id: DefId, pool: &Pool) -> String { } => { full_string.push_str(&format!( "Def2::ValueDef(identifier_id: >>{:?}), expr_id: >>{:?})", - pool.get(*identifier_id), + identifier_id, expr2_to_string(*expr_id, pool) )); } diff --git a/ast/src/lang/core/def/def_to_def2.rs b/ast/src/lang/core/def/def_to_def2.rs index ff659b4310..b7e83056c5 100644 --- a/ast/src/lang/core/def/def_to_def2.rs +++ b/ast/src/lang/core/def/def_to_def2.rs @@ -1,13 +1,10 @@ use bumpalo::collections::Vec as BumpVec; use bumpalo::Bump; -use roc_parse::{parser::SyntaxError, pattern::PatternType}; +use roc_module::ident::{Ident, IdentStr}; +use roc_parse::parser::SyntaxError; use roc_region::all::Region; -use crate::lang::{ - core::{expr::expr_to_expr2::loc_expr_to_expr2, pattern::to_pattern2}, - env::Env, - scope::Scope, -}; +use crate::lang::{core::expr::expr_to_expr2::loc_expr_to_expr2, env::Env, scope::Scope}; use super::def2::Def2; @@ -37,26 +34,18 @@ pub fn def_to_def2<'a>( SpaceBefore(inner_def, _) => def_to_def2(arena, env, scope, inner_def, region), SpaceAfter(inner_def, _) => def_to_def2(arena, env, scope, inner_def, region), Body(&loc_pattern, &loc_expr) => { - // TODO loc_pattern use identifier let expr2 = loc_expr_to_expr2(arena, loc_expr, env, scope, region).0; let expr_id = env.pool.add(expr2); use roc_parse::ast::Pattern::*; match loc_pattern.value { - Identifier(_) => { - let (_, pattern2) = to_pattern2( - env, - scope, - PatternType::TopLevelDef, - &loc_pattern.value, - region, - ); - let pattern_id = env.pool.add(pattern2); + Identifier(id_str) => { + let identifier_id = env.ident_ids.get_or_insert(&Ident(IdentStr::from(id_str))); // TODO support with annotation Def2::ValueDef { - identifier_id: pattern_id, + identifier_id, expr_id, } } diff --git a/ast/src/lang/core/expr/expr2.rs b/ast/src/lang/core/expr/expr2.rs index e06d169ece..5e7774ca98 100644 --- a/ast/src/lang/core/expr/expr2.rs +++ b/ast/src/lang/core/expr/expr2.rs @@ -95,7 +95,7 @@ pub enum Expr2 { }, Call { args: PoolVec<(Variable, ExprId)>, // 8B - expr: ExprId, // 4B + expr_id: ExprId, // 4B expr_var: Variable, // 4B fn_var: Variable, // 4B closure_var: Variable, // 4B @@ -108,11 +108,11 @@ pub enum Expr2 { }, Closure { args: PoolVec<(Variable, NodeId)>, // 8B - name: Symbol, // 8B - body: ExprId, // 4B - function_type: Variable, // 4B - recursive: Recursive, // 1B - extra: NodeId, // 4B + uniq_symbol: Symbol, // 8B This is a globally uniqe symbol for the closure + body_id: ExprId, // 4B + function_type: Variable, // 4B + recursive: Recursive, // 1B + extra: NodeId, // 4B }, // Product Types Record { diff --git a/ast/src/lang/core/expr/expr2_to_string.rs b/ast/src/lang/core/expr/expr2_to_string.rs index cbd2967766..05319e87d0 100644 --- a/ast/src/lang/core/expr/expr2_to_string.rs +++ b/ast/src/lang/core/expr/expr2_to_string.rs @@ -128,6 +128,26 @@ fn expr2_to_string_helper( pool.get(*body_id) )); } + Expr2::Call { .. } => { + out_string.push_str(&format!("Call({:?})", expr2,)); + } + Expr2::Closure { args, .. } => { + out_string.push_str("Closure:\n"); + out_string.push_str(&format!("{}args: [\n", get_spacing(indent_level + 1))); + + for (_, pattern_id) in args.iter(pool) { + let arg_pattern2 = pool.get(*pattern_id); + + out_string.push_str(&format!( + "{}{:?}\n", + get_spacing(indent_level + 2), + arg_pattern2 + )); + } + } + &Expr2::Var { .. } => { + out_string.push_str(&format!("{:?}", expr2,)); + } other => todo!("Implement for {:?}", other), } diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs index ebe8ba2fc9..7c7cfd2c3c 100644 --- a/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -232,7 +232,7 @@ pub fn expr_to_expr2<'a>( // env.problems.push(Problem::RuntimeError(error)); // // (answer, Output::default()) - todo!() + todo!("{:?}", &can_update) } } @@ -487,10 +487,10 @@ pub fn expr_to_expr2<'a>( ( Expr2::Closure { function_type: env.var_store.fresh(), - name: symbol, + uniq_symbol: symbol, recursive: Recursive::NotRecursive, args: can_args, - body: env.add(body_expr, loc_body_expr.region), + body_id: env.add(body_expr, loc_body_expr.region), extra: env.pool.add(extra), }, output, @@ -504,7 +504,6 @@ pub fn expr_to_expr2<'a>( // Canonicalize the function expression and its arguments let (fn_expr, mut output) = expr_to_expr2(env, scope, &loc_fn.value, fn_region); - // The function's return type let args = PoolVec::with_capacity(loc_args.len() as u32, env.pool); @@ -533,7 +532,7 @@ pub fn expr_to_expr2<'a>( let fn_expr_id = env.add(fn_expr, fn_region); Expr2::Call { args, - expr: fn_expr_id, + expr_id: fn_expr_id, expr_var: env.var_store.fresh(), fn_var: env.var_store.fresh(), closure_var: env.var_store.fresh(), @@ -571,7 +570,7 @@ pub fn expr_to_expr2<'a>( let fn_expr_id = env.add(fn_expr, fn_region); Expr2::Call { args, - expr: fn_expr_id, + expr_id: fn_expr_id, expr_var: env.var_store.fresh(), fn_var: env.var_store.fresh(), closure_var: env.var_store.fresh(), @@ -662,7 +661,10 @@ pub fn expr_to_expr2<'a>( // (RuntimeError(problem), Output::default()) todo!() } - Var { module_name, ident } => canonicalize_lookup(env, scope, module_name, ident, region), + Var { + module_name, // module_name will only be filled if the original Roc code stated something like `5 + SomeModule.myVar`, module_name will be blank if it was `5 + myVar` + ident, + } => canonicalize_lookup(env, scope, module_name, ident, region), // Below this point, we shouln't see any of these nodes anymore because // operator desugaring should have removed them! diff --git a/ast/src/lang/core/fun_def.rs b/ast/src/lang/core/fun_def.rs index 588d07d996..02d3bdbab4 100644 --- a/ast/src/lang/core/fun_def.rs +++ b/ast/src/lang/core/fun_def.rs @@ -18,13 +18,13 @@ pub enum FunctionDef { arguments: PoolVec<(PatternId, Type2)>, // 8B rigids: NodeId, // 4B return_type: TypeId, // 4B - body: ExprId, // 4B + body_id: ExprId, // 4B }, NoAnnotation { name: Symbol, // 8B arguments: PoolVec<(PatternId, Variable)>, // 8B return_var: Variable, // 4B - body: ExprId, // 4B + body_id: ExprId, // 4B }, } @@ -36,25 +36,25 @@ impl ShallowClone for FunctionDef { arguments, rigids, return_type, - body, + body_id, } => Self::WithAnnotation { name: *name, arguments: arguments.shallow_clone(), rigids: *rigids, return_type: *return_type, - body: *body, + body_id: *body_id, }, Self::NoAnnotation { name, arguments, return_var, - body, + body_id, } => Self::NoAnnotation { name: *name, arguments: arguments.shallow_clone(), return_var: *return_var, - body: *body, + body_id: *body_id, }, } } diff --git a/ast/src/lang/core/str.rs b/ast/src/lang/core/str.rs index 7a939aac33..47177b2e08 100644 --- a/ast/src/lang/core/str.rs +++ b/ast/src/lang/core/str.rs @@ -157,7 +157,7 @@ fn desugar_str_segments(env: &mut Env, segments: Vec) -> Expr2 { let new_call = Expr2::Call { args, - expr: concat_expr_id, + expr_id: concat_expr_id, expr_var: var_store.fresh(), fn_var: var_store.fresh(), closure_var: var_store.fresh(), diff --git a/ast/src/lang/env.rs b/ast/src/lang/env.rs index 87a048151e..03d93f561e 100644 --- a/ast/src/lang/env.rs +++ b/ast/src/lang/env.rs @@ -1,3 +1,4 @@ +use crate::mem_pool::pool::{NodeId, Pool}; use bumpalo::{collections::Vec as BumpVec, Bump}; use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{Ident, ModuleName}; @@ -6,8 +7,6 @@ use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Located, Region}; use roc_types::subs::VarStore; -use crate::mem_pool::pool::{NodeId, Pool}; - use super::core::def::def::References; #[derive(Debug)] @@ -35,6 +34,7 @@ pub struct Env<'a> { } impl<'a> Env<'a> { + #[allow(clippy::too_many_arguments)] pub fn new( home: ModuleId, arena: &'a Bump, @@ -52,7 +52,7 @@ impl<'a> Env<'a> { var_store, dep_idents, module_ids, - ident_ids: exposed_ident_ids.clone(), // we start with these, but will add more later + ident_ids: exposed_ident_ids.clone(), // we start with these, but will add more later using Scope.introduce exposed_ident_ids, closures: MutMap::default(), qualified_lookups: MutSet::default(), diff --git a/ast/src/lang/scope.rs b/ast/src/lang/scope.rs index 9f3a11c60c..5efe4f118c 100644 --- a/ast/src/lang/scope.rs +++ b/ast/src/lang/scope.rs @@ -2,13 +2,18 @@ #![allow(dead_code)] #![allow(unused_imports)] +use std::fmt; + +use crate::ast_error::ASTResult; use crate::mem_pool::pool::Pool; use crate::mem_pool::pool_str::PoolStr; use crate::mem_pool::pool_vec::PoolVec; use crate::mem_pool::shallow_clone::ShallowClone; use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{Ident, Lowercase}; -use roc_module::symbol::{IdentIds, ModuleId, Symbol}; +use roc_module::symbol::{ + get_module_ident_ids, get_module_ident_ids_mut, IdentIds, Interns, ModuleId, Symbol, +}; use roc_problem::can::RuntimeError; use roc_region::all::{Located, Region}; use roc_types::{ @@ -18,6 +23,7 @@ use roc_types::{ }; use super::core::types::{Alias, Type2, TypeId}; +use super::env::Env; fn solved_type_to_type_id( pool: &mut Pool, @@ -312,6 +318,25 @@ impl Scope { pub fn contains_alias(&mut self, name: Symbol) -> bool { self.aliases.contains_key(&name) } + + pub fn fill_scope( + &mut self, + env: &Env, + all_ident_ids: &mut MutMap, + ) -> ASTResult<()> { + let ident_ids = get_module_ident_ids(all_ident_ids, &env.home)?.clone(); + + for (_, ident_ref) in ident_ids.idents() { + self.introduce( + ident_ref.as_inline_str().as_str().into(), + &env.exposed_ident_ids, + get_module_ident_ids_mut(all_ident_ids, &env.home)?, + Region::zero(), + )?; + } + + Ok(()) + } } impl ShallowClone for Scope { 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/ast/src/parse/parse_ast.rs b/ast/src/parse/parse_ast.rs index 3ae3cca0d7..0fd5a15317 100644 --- a/ast/src/parse/parse_ast.rs +++ b/ast/src/parse/parse_ast.rs @@ -1,15 +1,18 @@ use bumpalo::Bump; -use roc_parse::parser::SyntaxError; +use roc_module::symbol::Interns; use roc_region::all::Region; -use crate::lang::{ - core::{ - ast::AST, - def::{def2::DefId, def_to_def2::str_to_def2}, - expr::expr2::Expr2, +use crate::{ + ast_error::ASTResult, + lang::{ + core::{ + ast::AST, + def::{def2::DefId, def_to_def2::str_to_def2}, + expr::expr2::Expr2, + }, + env::Env, + scope::Scope, }, - env::Env, - scope::Scope, }; use super::parse_header; @@ -18,7 +21,8 @@ pub fn parse_from_string<'a>( code_str: &'a str, env: &mut Env<'a>, ast_arena: &'a Bump, -) -> Result> { + interns: &mut Interns, +) -> ASTResult { let blank_line_indx = code_str .find("\n\n") .expect("I was expecting a double newline to split header and rest of code."); @@ -27,6 +31,8 @@ pub fn parse_from_string<'a>( let tail_str = &code_str[blank_line_indx..]; let mut scope = Scope::new(env.home, env.pool, env.var_store); + scope.fill_scope(env, &mut interns.all_ident_ids)?; + let region = Region::new(0, 0, 0, 0); let mut def_ids = Vec::::new(); diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 97ecb81a46..7f46d6117c 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -58,15 +58,13 @@ roc_fmt = { path = "../compiler/fmt" } roc_reporting = { path = "../compiler/reporting" } roc_editor = { path = "../editor", optional = true } roc_linker = { path = "../linker" } -# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it -clap = { git = "https://github.com/rtfeldman/clap", branch = "master" } +clap = "= 3.0.0-beta.1" const_format = "0.2" rustyline = { git = "https://github.com/rtfeldman/rustyline", tag = "prompt-fix" } rustyline-derive = { git = "https://github.com/rtfeldman/rustyline", tag = "prompt-fix" } 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 } @@ -79,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" @@ -91,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 bfd4faea6e..44da4b3772 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,6 +1,3 @@ -#[macro_use] -extern crate clap; - #[macro_use] extern crate const_format; @@ -21,7 +18,6 @@ use target_lexicon::{Architecture, OperatingSystem, Triple, X86_32Architecture}; pub mod build; pub mod repl; -pub const CMD_RUN: &str = "run"; pub const CMD_BUILD: &str = "build"; pub const CMD_REPL: &str = "repl"; pub const CMD_EDIT: &str = "edit"; @@ -43,31 +39,31 @@ pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP"; pub fn build_app<'a>() -> App<'a> { let app = App::new("roc") - .version(concatcp!(crate_version!(), "\n")) + .version(concatcp!(include_str!("../../version.txt"), "\n")) .about("Runs the given .roc file. Use one of the SUBCOMMANDS below to do something else!") .subcommand(App::new(CMD_BUILD) .about("Build a binary from the given .roc file, but don't run it") .arg( Arg::with_name(ROC_FILE) - .help("The .roc file to build") + .about("The .roc file to build") .required(true), ) .arg( Arg::with_name(FLAG_OPTIMIZE) .long(FLAG_OPTIMIZE) - .help("Optimize your compiled Roc program to run faster. (Optimization takes time to complete.)") + .about("Optimize your compiled Roc program to run faster. (Optimization takes time to complete.)") .required(false), ) .arg( Arg::with_name(FLAG_DEV) .long(FLAG_DEV) - .help("Make compilation as fast as possible. (Runtime performance may suffer)") + .about("Make compilation as fast as possible. (Runtime performance may suffer)") .required(false), ) .arg( Arg::with_name(FLAG_BACKEND) .long(FLAG_BACKEND) - .help("Choose a different backend") + .about("Choose a different backend") // .requires(BACKEND) .default_value(Backend::default().as_str()) .possible_values(Backend::OPTIONS) @@ -76,66 +72,34 @@ pub fn build_app<'a>() -> App<'a> { .arg( Arg::with_name(FLAG_LIB) .long(FLAG_LIB) - .help("Build a C library instead of an executable.") + .about("Build a C library instead of an executable.") .required(false), ) .arg( Arg::with_name(FLAG_DEBUG) .long(FLAG_DEBUG) - .help("Store LLVM debug information in the generated program") + .about("Store LLVM debug information in the generated program") .required(false), ) .arg( Arg::with_name(FLAG_TIME) .long(FLAG_TIME) - .help("Prints detailed compilation time information.") + .about("Prints detailed compilation time information.") .required(false), ) .arg( Arg::with_name(FLAG_LINK) .long(FLAG_LINK) - .help("Uses the roc linker instead of the system linker.") + .about("Uses the roc linker instead of the system linker.") .required(false), ) .arg( Arg::with_name(FLAG_PRECOMPILED) .long(FLAG_PRECOMPILED) - .help("Assumes the host has been precompiled and skips recompiling the host.") + .about("Assumes the host has been precompiled and skips recompiling the host.") .required(false), ) ) - .subcommand(App::new(CMD_RUN) - .about("DEPRECATED - now use `roc [FILE]` instead of `roc run [FILE]`") - .setting(AppSettings::TrailingVarArg) - .arg( - Arg::with_name(FLAG_OPTIMIZE) - .long(FLAG_OPTIMIZE) - .help("Optimize the compiled program to run faster. (Optimization takes time to complete.)") - .required(false), - ) - .arg( - Arg::with_name(FLAG_DEV) - .long(FLAG_DEV) - .help("Make compilation as fast as possible. (Runtime performance may suffer)") - .required(false), - ) - .arg( - Arg::with_name(FLAG_DEBUG) - .long(FLAG_DEBUG) - .help("Store LLVM debug information in the generated program") - .required(false), - ) - .arg( - Arg::with_name(ROC_FILE) - .help("The .roc file of an app to run") - .required(true), - ) - .arg( - Arg::with_name(ARGS_FOR_APP) - .help("Arguments to pass into the app being run") - .multiple(true), - ) - ) .subcommand(App::new(CMD_REPL) .about("Launch the interactive Read Eval Print Loop (REPL)") ) @@ -144,23 +108,23 @@ pub fn build_app<'a>() -> App<'a> { .arg( Arg::with_name(FLAG_TIME) .long(FLAG_TIME) - .help("Prints detailed compilation time information.") + .about("Prints detailed compilation time information.") .required(false), ) .arg( Arg::with_name(ROC_FILE) - .help("The .roc file of an app to run") + .about("The .roc file of an app to run") .required(true), ) ) .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) .required(false) - .help("The directory or files to build documentation for") + .about("The directory or files to build documentation for") ) ) @@ -168,45 +132,45 @@ pub fn build_app<'a>() -> App<'a> { .arg( Arg::with_name(FLAG_OPTIMIZE) .long(FLAG_OPTIMIZE) - .help("Optimize the compiled program to run faster. (Optimization takes time to complete.)") + .about("Optimize the compiled program to run faster. (Optimization takes time to complete.)") .requires(ROC_FILE) .required(false), ) .arg( Arg::with_name(FLAG_DEV) .long(FLAG_DEV) - .help("Make compilation as fast as possible. (Runtime performance may suffer)") + .about("Make compilation as fast as possible. (Runtime performance may suffer)") .required(false), ) .arg( Arg::with_name(FLAG_DEBUG) .long(FLAG_DEBUG) - .help("Store LLVM debug information in the generated program") + .about("Store LLVM debug information in the generated program") .requires(ROC_FILE) .required(false), ) .arg( Arg::with_name(FLAG_TIME) .long(FLAG_TIME) - .help("Prints detailed compilation time information.") + .about("Prints detailed compilation time information.") .required(false), ) .arg( Arg::with_name(FLAG_LINK) .long(FLAG_LINK) - .help("Uses the roc linker instead of the system linker.") + .about("Uses the roc linker instead of the system linker.") .required(false), ) .arg( Arg::with_name(FLAG_PRECOMPILED) .long(FLAG_PRECOMPILED) - .help("Assumes the host has been precompiled and skips recompiling the host.") + .about("Assumes the host has been precompiled and skips recompiling the host.") .required(false), ) .arg( Arg::with_name(FLAG_BACKEND) .long(FLAG_BACKEND) - .help("Choose a different backend") + .about("Choose a different backend") // .requires(BACKEND) .default_value(Backend::default().as_str()) .possible_values(Backend::OPTIONS) @@ -214,12 +178,12 @@ pub fn build_app<'a>() -> App<'a> { ) .arg( Arg::with_name(ROC_FILE) - .help("The .roc file of an app to build and run") + .about("The .roc file of an app to build and run") .required(false), ) .arg( Arg::with_name(ARGS_FOR_APP) - .help("Arguments to pass into the app being run") + .about("Arguments to pass into the app being run") .requires(ROC_FILE) .multiple(true), ); @@ -231,7 +195,7 @@ pub fn build_app<'a>() -> App<'a> { .index(1) .multiple(true) .required(false) - .help("(optional) The directory or files to open on launch."), + .about("(optional) The directory or files to open on launch."), ), ) } else { diff --git a/cli/src/main.rs b/cli/src/main.rs index 52f065a386..252aded55a 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,7 +1,7 @@ use roc_cli::build::check_file; use roc_cli::{ build_app, docs, repl, BuildConfig, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT, CMD_REPL, - CMD_RUN, DIRECTORY_OR_FILES, FLAG_TIME, ROC_FILE, + DIRECTORY_OR_FILES, FLAG_TIME, ROC_FILE, }; use roc_load::file::LoadingProblem; use std::fs::{self, FileType}; @@ -44,17 +44,6 @@ fn main() -> io::Result<()> { matches.subcommand_matches(CMD_BUILD).unwrap(), BuildConfig::BuildOnly, )?), - Some(CMD_RUN) => { - // TODO remove CMD_RUN altogether if it is currently September 2021 or later. - println!( - r#"`roc run` is deprecated! -If you're using a prebuilt binary, you no longer need the `run` - just do `roc [FILE]` instead of `roc run [FILE]`. -If you're building the compiler from source you'll want to do `cargo run [FILE]` instead of `cargo run run [FILE]`. -"# - ); - - Ok(1) - } Some(CMD_CHECK) => { let arena = bumpalo::Bump::new(); diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 0eefb0107c..3251e3b48b 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -129,7 +129,7 @@ mod cli_run { if !&out.stdout.ends_with(expected_ending) { panic!( "expected output to end with {:?} but instead got {:#?}", - expected_ending, out + expected_ending, out.stdout ); } assert!(out.status.success()); @@ -191,6 +191,12 @@ mod cli_run { eprintln!("WARNING: skipping testing example {} because the test is broken right now!", example.filename); return; } + "hello-swift" => { + if cfg!(not(target_os = "macos")) { + eprintln!("WARNING: skipping testing example {} because it only works on MacOS.", example.filename); + return; + } + } _ => {} } @@ -205,6 +211,9 @@ mod cli_run { example.use_valgrind, ); + // This is mostly because the false interpreter is still very slow - + // 25s for the cli tests is just not acceptable during development! + #[cfg(not(debug_assertions))] check_output_with_stdin( &file_name, example.stdin, @@ -280,6 +289,14 @@ mod cli_run { expected_ending:"Hello, World!\n", use_valgrind: true, }, + hello_swift:"hello-swift" => Example { + filename: "Hello.roc", + executable_filename: "hello-swift", + stdin: &[], + input_file: None, + expected_ending:"Hello Swift, meet Roc\n", + use_valgrind: true, + }, hello_web:"hello-web" => Example { filename: "Hello.roc", executable_filename: "hello-web", @@ -359,7 +376,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/code_markup/Cargo.toml b/code_markup/Cargo.toml index 2583ee0006..05258d219c 100644 --- a/code_markup/Cargo.toml +++ b/code_markup/Cargo.toml @@ -14,5 +14,6 @@ serde = { version = "1.0.123", features = ["derive"] } palette = "0.5" snafu = { version = "0.6", features = ["backtraces"] } bumpalo = { version = "3.2", features = ["collections"] } +itertools = "0.10.1" [dev-dependencies] \ No newline at end of file diff --git a/code_markup/src/markup/common_nodes.rs b/code_markup/src/markup/common_nodes.rs index 806d6170cd..56737d9073 100644 --- a/code_markup/src/markup/common_nodes.rs +++ b/code_markup/src/markup/common_nodes.rs @@ -16,9 +16,13 @@ pub fn new_equals_mn(ast_node_id: ASTNodeId, parent_id_opt: Option) } pub fn new_comma_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + new_comma_mn_ast(ASTNodeId::AExprId(expr_id), parent_id_opt) +} + +pub fn new_comma_mn_ast(ast_node_id: ASTNodeId, parent_id_opt: Option) -> MarkupNode { MarkupNode::Text { content: nodes::COMMA.to_owned(), - ast_node_id: ASTNodeId::AExprId(expr_id), + ast_node_id, syn_high_style: HighlightStyle::Comma, attributes: Attributes::default(), parent_id_opt, @@ -29,7 +33,6 @@ pub fn new_comma_mn(expr_id: ExprId, parent_id_opt: Option) -> Marku pub fn new_blank_mn(ast_node_id: ASTNodeId, parent_id_opt: Option) -> MarkupNode { MarkupNode::Blank { ast_node_id, - syn_high_style: HighlightStyle::Blank, attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, @@ -43,7 +46,6 @@ pub fn new_blank_mn_w_nls( ) -> MarkupNode { MarkupNode::Blank { ast_node_id, - syn_high_style: HighlightStyle::Blank, attributes: Attributes::default(), parent_id_opt, newlines_at_end: nr_of_newlines, @@ -51,8 +53,16 @@ pub fn new_blank_mn_w_nls( } pub fn new_colon_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + new_operator_mn(nodes::COLON.to_owned(), expr_id, parent_id_opt) +} + +pub fn new_operator_mn( + content: String, + expr_id: ExprId, + parent_id_opt: Option, +) -> MarkupNode { MarkupNode::Text { - content: nodes::COLON.to_owned(), + content, ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::Operator, attributes: Attributes::default(), @@ -104,3 +114,36 @@ pub fn new_right_square_mn(expr_id: ExprId, parent_id_opt: Option) - newlines_at_end: 0, } } + +pub fn new_func_name_mn(content: String, expr_id: ExprId) -> MarkupNode { + MarkupNode::Text { + content, + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::FunctionName, + attributes: Attributes::default(), + parent_id_opt: None, + newlines_at_end: 0, + } +} + +pub fn new_arg_name_mn(content: String, expr_id: ExprId) -> MarkupNode { + MarkupNode::Text { + content, + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::FunctionArgName, + attributes: Attributes::default(), + parent_id_opt: None, + newlines_at_end: 0, + } +} + +pub fn new_arrow_mn(ast_node_id: ASTNodeId, newlines_at_end: usize) -> MarkupNode { + MarkupNode::Text { + content: nodes::ARROW.to_owned(), + ast_node_id, + syn_high_style: HighlightStyle::Operator, + attributes: Attributes::default(), + parent_id_opt: None, + newlines_at_end, + } +} diff --git a/code_markup/src/markup/convert/from_ast.rs b/code_markup/src/markup/convert/from_ast.rs new file mode 100644 index 0000000000..006ece4a3e --- /dev/null +++ b/code_markup/src/markup/convert/from_ast.rs @@ -0,0 +1,34 @@ +use roc_ast::{ + ast_error::ASTResult, + lang::{core::ast::AST, env::Env}, +}; +use roc_module::symbol::Interns; + +use crate::{ + markup::{ + convert::{from_def2::def2_to_markup, from_header::header_to_markup}, + nodes::set_parent_for_all, + }, + slow_pool::{MarkNodeId, SlowPool}, +}; + +pub fn ast_to_mark_nodes<'a>( + env: &mut Env<'a>, + ast: &AST, + mark_node_pool: &mut SlowPool, + interns: &Interns, +) -> ASTResult> { + let mut all_mark_node_ids = vec![header_to_markup(&ast.header, mark_node_pool)]; + + for &def_id in ast.def_ids.iter() { + let def2 = env.pool.get(def_id); + + let expr2_markup_id = def2_to_markup(env, def2, def_id, mark_node_pool, interns)?; + + set_parent_for_all(expr2_markup_id, mark_node_pool); + + all_mark_node_ids.push(expr2_markup_id); + } + + Ok(all_mark_node_ids) +} diff --git a/code_markup/src/markup/convert/from_def2.rs b/code_markup/src/markup/convert/from_def2.rs new file mode 100644 index 0000000000..6ceaa07ef2 --- /dev/null +++ b/code_markup/src/markup/convert/from_def2.rs @@ -0,0 +1,52 @@ +use crate::{ + markup::{common_nodes::new_blank_mn_w_nls, top_level_def::tld_mark_node}, + slow_pool::{MarkNodeId, SlowPool}, +}; + +use super::from_expr2::expr2_to_markup; + +use roc_ast::{ + ast_error::ASTResult, + lang::{ + core::{ + ast::ASTNodeId, + def::def2::{Def2, DefId}, + }, + env::Env, + }, +}; +use roc_module::symbol::Interns; + +pub fn def2_to_markup<'a>( + env: &mut Env<'a>, + def2: &Def2, + def2_node_id: DefId, + mark_node_pool: &mut SlowPool, + interns: &Interns, +) -> ASTResult { + let ast_node_id = ASTNodeId::ADefId(def2_node_id); + + let mark_node_id = match def2 { + Def2::ValueDef { + identifier_id, + expr_id, + } => { + let expr_mn_id = expr2_to_markup( + env, + env.pool.get(*expr_id), + *expr_id, + mark_node_pool, + interns, + 0, + )?; + + let tld_mn = + tld_mark_node(*identifier_id, expr_mn_id, ast_node_id, mark_node_pool, env)?; + + mark_node_pool.add(tld_mn) + } + Def2::Blank => mark_node_pool.add(new_blank_mn_w_nls(ast_node_id, None, 2)), + }; + + Ok(mark_node_id) +} diff --git a/code_markup/src/markup/convert/from_expr2.rs b/code_markup/src/markup/convert/from_expr2.rs new file mode 100644 index 0000000000..d36d1d448f --- /dev/null +++ b/code_markup/src/markup/convert/from_expr2.rs @@ -0,0 +1,389 @@ +use crate::{ + markup::{ + attribute::Attributes, + common_nodes::{ + new_arg_name_mn, new_arrow_mn, new_blank_mn, new_colon_mn, new_comma_mn, new_equals_mn, + new_left_accolade_mn, new_left_square_mn, new_operator_mn, new_right_accolade_mn, + new_right_square_mn, + }, + nodes::{ + get_string, join_mark_nodes_commas, join_mark_nodes_spaces, new_markup_node, MarkupNode, + }, + }, + slow_pool::{MarkNodeId, SlowPool}, + syntax_highlight::HighlightStyle, +}; + +use itertools::Itertools; +use roc_ast::{ + ast_error::ASTResult, + lang::{ + core::{ + ast::ASTNodeId, + expr::{ + expr2::{Expr2, ExprId}, + record_field::RecordField, + }, + pattern::{get_identifier_string, Pattern2}, + val_def::ValueDef, + }, + env::Env, + }, +}; +use roc_module::{module_err::ModuleResult, symbol::Interns}; + +// make Markup Nodes: generate String representation, assign Highlighting Style +pub fn expr2_to_markup<'a>( + env: &Env<'a>, + expr2: &Expr2, + expr2_node_id: ExprId, + mark_node_pool: &mut SlowPool, + interns: &Interns, + indent_level: usize, +) -> ASTResult { + let ast_node_id = ASTNodeId::AExprId(expr2_node_id); + + // for debugging + //println!("EXPR2 {:?}", expr2); + + let mark_node_id = match expr2 { + Expr2::SmallInt { text, .. } + | Expr2::I128 { text, .. } + | Expr2::U128 { text, .. } + | Expr2::Float { text, .. } => { + let num_str = get_string(env, text); + + new_markup_node( + with_indent(indent_level, &num_str), + ast_node_id, + HighlightStyle::Number, + mark_node_pool, + indent_level, + ) + } + Expr2::Str(text) => { + let content = format!("\"{}\"", text.as_str(env.pool)); + + new_markup_node( + with_indent(indent_level, &content), + ast_node_id, + HighlightStyle::String, + mark_node_pool, + indent_level, + ) + } + Expr2::GlobalTag { name, .. } => new_markup_node( + with_indent(indent_level, &get_string(env, name)), + ast_node_id, + HighlightStyle::Type, + mark_node_pool, + indent_level, + ), + Expr2::Call { args, expr_id, .. } => { + let expr = env.pool.get(*expr_id); + let fun_call_mark_id = + expr2_to_markup(env, expr, *expr_id, mark_node_pool, interns, indent_level)?; + + let arg_expr_ids: Vec = + args.iter(env.pool).map(|(_, arg_id)| *arg_id).collect(); + + let arg_call_mark_ids: Vec = arg_expr_ids + .iter() + .map(|arg_id| { + let arg_expr = env.pool.get(*arg_id); + + expr2_to_markup(env, arg_expr, *arg_id, mark_node_pool, interns, 0) + }) + .collect::>>()?; + + let mut args_with_sapces = + join_mark_nodes_spaces(arg_call_mark_ids, true, ast_node_id, mark_node_pool); + + let mut children_ids = vec![fun_call_mark_id]; + children_ids.append(&mut args_with_sapces); + + let call_node = MarkupNode::Nested { + ast_node_id, + children_ids, + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(call_node) + } + Expr2::Var(symbol) => { + let text = symbol.fully_qualified(interns, env.home); + + new_markup_node( + text.to_string(), + ast_node_id, + HighlightStyle::Value, + mark_node_pool, + indent_level, + ) + } + Expr2::List { elems, .. } => { + let mut children_ids = + vec![mark_node_pool.add(new_left_square_mn(expr2_node_id, None))]; + + let indexed_node_ids: Vec<(usize, ExprId)> = + elems.iter(env.pool).copied().enumerate().collect(); + + for (idx, node_id) in indexed_node_ids.iter() { + let sub_expr2 = env.pool.get(*node_id); + + children_ids.push(expr2_to_markup( + env, + sub_expr2, + *node_id, + mark_node_pool, + interns, + indent_level, + )?); + + if idx + 1 < elems.len() { + children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None))); + } + } + children_ids.push(mark_node_pool.add(new_right_square_mn(expr2_node_id, None))); + + let list_node = MarkupNode::Nested { + ast_node_id, + children_ids, + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(list_node) + } + Expr2::EmptyRecord => { + let children_ids = vec![ + mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None)), + mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None)), + ]; + + let record_node = MarkupNode::Nested { + ast_node_id, + children_ids, + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(record_node) + } + Expr2::Record { fields, .. } => { + let mut children_ids = + vec![mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None))]; + + for (idx, field_node_id) in fields.iter_node_ids().enumerate() { + let record_field = env.pool.get(field_node_id); + + let field_name = record_field.get_record_field_pool_str(); + + children_ids.push(new_markup_node( + field_name.as_str(env.pool).to_owned(), + ast_node_id, + HighlightStyle::RecordField, + mark_node_pool, + indent_level, + )); + + match record_field { + RecordField::InvalidLabelOnly(_, _) => (), + RecordField::LabelOnly(_, _, _) => (), + RecordField::LabeledValue(_, _, sub_expr2_node_id) => { + children_ids.push(mark_node_pool.add(new_colon_mn(expr2_node_id, None))); + + let sub_expr2 = env.pool.get(*sub_expr2_node_id); + children_ids.push(expr2_to_markup( + env, + sub_expr2, + *sub_expr2_node_id, + mark_node_pool, + interns, + indent_level, + )?); + } + } + + if idx + 1 < fields.len() { + children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None))); + } + } + + children_ids.push(mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None))); + + let record_node = MarkupNode::Nested { + ast_node_id, + children_ids, + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(record_node) + } + Expr2::Blank => mark_node_pool.add(new_blank_mn(ast_node_id, None)), + Expr2::LetValue { + def_id, + body_id: _, + body_var: _, + } => { + let pattern_id = env.pool.get(*def_id).get_pattern_id(); + + let pattern2 = env.pool.get(pattern_id); + + let val_name = get_identifier_string(pattern2, interns)?; + + let val_name_mn = MarkupNode::Text { + content: val_name, + ast_node_id, + syn_high_style: HighlightStyle::Value, + attributes: Attributes::default(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + let val_name_mn_id = mark_node_pool.add(val_name_mn); + + let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None)); + + let value_def = env.pool.get(*def_id); + + match value_def { + ValueDef::NoAnnotation { + pattern_id: _, + expr_id, + expr_var: _, + } => { + let body_mn_id = expr2_to_markup( + env, + env.pool.get(*expr_id), + *expr_id, + mark_node_pool, + interns, + indent_level, + )?; + + let body_mn = mark_node_pool.get_mut(body_mn_id); + body_mn.add_newline_at_end(); + + let full_let_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![val_name_mn_id, equals_mn_id, body_mn_id], + parent_id_opt: None, + newlines_at_end: 1, + }; + + mark_node_pool.add(full_let_node) + } + other => { + unimplemented!( + "I don't know how to convert {:?} into a MarkupNode yet.", + other + ) + } + } + } + Expr2::Closure { + function_type: _, + uniq_symbol: _, + recursive: _, + args, + body_id, + extra: _, + } => { + let backslash_mn = new_operator_mn("\\".to_string(), expr2_node_id, None); + let backslash_mn_id = mark_node_pool.add(backslash_mn); + + let arg_names: Vec<&str> = args + .iter(env.pool) + .map(|(_, arg_node_id)| { + let arg_pattern2 = env.pool.get(*arg_node_id); + + match arg_pattern2 { + Pattern2::Identifier(id_symbol) => { + let ident_id = id_symbol.ident_id(); + + env.ident_ids.get_name_str_res(ident_id) + } + Pattern2::Shadowed { shadowed_ident } => { + Ok(shadowed_ident.as_str(env.pool)) + } + other => { + todo!( + "TODO: support the following pattern2 as function arg: {:?}", + other + ); + } + } + }) + .collect::>>()?; + + let arg_mark_nodes = arg_names + .iter() + .map(|arg_name| new_arg_name_mn(arg_name.to_string(), expr2_node_id)) + .collect_vec(); + + let args_with_commas: Vec = + join_mark_nodes_commas(arg_mark_nodes, ASTNodeId::AExprId(expr2_node_id)); + + let mut args_with_commas_ids: Vec = args_with_commas + .into_iter() + .map(|mark_node| mark_node_pool.add(mark_node)) + .collect(); + + let arrow_mn = new_arrow_mn(ASTNodeId::AExprId(expr2_node_id), 1); + let arrow_mn_id = mark_node_pool.add(arrow_mn); + + let mut children_ids = vec![backslash_mn_id]; + children_ids.append(&mut args_with_commas_ids); + children_ids.push(arrow_mn_id); + + let args_mn = MarkupNode::Nested { + ast_node_id: ASTNodeId::AExprId(expr2_node_id), + children_ids, + parent_id_opt: None, + newlines_at_end: 0, + }; + let args_mn_id = mark_node_pool.add(args_mn); + + let body_expr = env.pool.get(*body_id); + let body_mn_id = expr2_to_markup( + env, + body_expr, + *body_id, + mark_node_pool, + interns, + indent_level + 1, + )?; + + let function_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![args_mn_id, body_mn_id], + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(function_node) + } + Expr2::RuntimeError() => new_markup_node( + "RunTimeError".to_string(), + ast_node_id, + HighlightStyle::Blank, + mark_node_pool, + indent_level, + ), + rest => todo!("implement expr2_to_markup for {:?}", rest), + }; + + Ok(mark_node_id) +} + +fn with_indent(indent_level: usize, some_str: &str) -> String { + let full_indent = std::iter::repeat(" ").take(indent_level * 4); + let mut full_string: String = full_indent.collect(); + + full_string.push_str(some_str); + + full_string +} diff --git a/code_markup/src/markup/convert/from_header.rs b/code_markup/src/markup/convert/from_header.rs new file mode 100644 index 0000000000..7500b44714 --- /dev/null +++ b/code_markup/src/markup/convert/from_header.rs @@ -0,0 +1,205 @@ +use roc_ast::lang::core::{ast::ASTNodeId, expr::expr2::ExprId, header::AppHeader}; + +use crate::{ + markup::{ + attribute::Attributes, + common_nodes::{ + new_comma_mn, new_left_accolade_mn, new_left_square_mn, new_right_accolade_mn, + new_right_square_mn, + }, + nodes::{set_parent_for_all, MarkupNode}, + }, + slow_pool::{MarkNodeId, SlowPool}, + syntax_highlight::HighlightStyle, +}; + +pub fn header_to_markup(app_header: &AppHeader, mark_node_pool: &mut SlowPool) -> MarkNodeId { + let expr_id = app_header.ast_node_id; + let ast_node_id = ASTNodeId::AExprId(expr_id); + + let app_node_id = header_mn("app ".to_owned(), expr_id, mark_node_pool); + + let app_name_node_id = header_val_mn( + app_header.app_name.clone(), + expr_id, + HighlightStyle::String, + mark_node_pool, + ); + + let full_app_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![app_node_id, app_name_node_id], + parent_id_opt: None, + newlines_at_end: 1, + }; + + let packages_node_id = header_mn(" packages ".to_owned(), expr_id, mark_node_pool); + + let pack_left_acc_node_id = mark_node_pool.add(new_left_accolade_mn(expr_id, None)); + + let pack_base_node_id = header_val_mn( + "base: ".to_owned(), + expr_id, + HighlightStyle::RecordField, + mark_node_pool, + ); + + let pack_val_node_id = header_val_mn( + app_header.packages_base.clone(), + expr_id, + HighlightStyle::String, + mark_node_pool, + ); + + let pack_right_acc_node_id = mark_node_pool.add(new_right_accolade_mn(expr_id, None)); + + let full_packages_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![ + packages_node_id, + pack_left_acc_node_id, + pack_base_node_id, + pack_val_node_id, + pack_right_acc_node_id, + ], + parent_id_opt: None, + newlines_at_end: 1, + }; + + let imports_node_id = header_mn(" imports ".to_owned(), expr_id, mark_node_pool); + + let imports_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None)); + + let mut import_child_ids: Vec = add_header_mn_list( + &app_header.imports, + expr_id, + HighlightStyle::Import, + mark_node_pool, + ); + + let imports_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None)); + + let mut full_import_children = vec![imports_node_id, imports_left_square_node_id]; + + full_import_children.append(&mut import_child_ids); + full_import_children.push(imports_right_square_node_id); + + let full_import_node = MarkupNode::Nested { + ast_node_id, + children_ids: full_import_children, + parent_id_opt: None, + newlines_at_end: 1, + }; + + let provides_node_id = header_mn(" provides ".to_owned(), expr_id, mark_node_pool); + + let provides_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None)); + + let mut provides_val_node_ids: Vec = add_header_mn_list( + &app_header.provides, + expr_id, + HighlightStyle::Provides, + mark_node_pool, + ); + + let provides_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None)); + + let provides_end_node_id = header_mn(" to base".to_owned(), expr_id, mark_node_pool); + + let mut full_provides_children = vec![provides_node_id, provides_left_square_node_id]; + + full_provides_children.append(&mut provides_val_node_ids); + full_provides_children.push(provides_right_square_node_id); + full_provides_children.push(provides_end_node_id); + + let full_provides_node = MarkupNode::Nested { + ast_node_id, + children_ids: full_provides_children, + parent_id_opt: None, + newlines_at_end: 1, + }; + + let full_app_node_id = mark_node_pool.add(full_app_node); + let full_packages_node = mark_node_pool.add(full_packages_node); + let full_import_node_id = mark_node_pool.add(full_import_node); + let full_provides_node_id = mark_node_pool.add(full_provides_node); + + let header_mark_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![ + full_app_node_id, + full_packages_node, + full_import_node_id, + full_provides_node_id, + ], + parent_id_opt: None, + newlines_at_end: 1, + }; + + let header_mn_id = mark_node_pool.add(header_mark_node); + + set_parent_for_all(header_mn_id, mark_node_pool); + + header_mn_id +} + +// Used for provides and imports +fn add_header_mn_list( + str_vec: &[String], + expr_id: ExprId, + highlight_style: HighlightStyle, + mark_node_pool: &mut SlowPool, +) -> Vec { + let nr_of_elts = str_vec.len(); + + str_vec + .iter() + .enumerate() + .map(|(indx, provide_str)| { + let provide_str = header_val_mn( + provide_str.to_owned(), + expr_id, + highlight_style, + mark_node_pool, + ); + + if indx != nr_of_elts - 1 { + vec![provide_str, mark_node_pool.add(new_comma_mn(expr_id, None))] + } else { + vec![provide_str] + } + }) + .flatten() + .collect() +} + +fn header_mn(content: String, expr_id: ExprId, mark_node_pool: &mut SlowPool) -> MarkNodeId { + let mark_node = MarkupNode::Text { + content, + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::PackageRelated, + attributes: Attributes::default(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(mark_node) +} + +fn header_val_mn( + content: String, + expr_id: ExprId, + highlight_style: HighlightStyle, + mark_node_pool: &mut SlowPool, +) -> MarkNodeId { + let mark_node = MarkupNode::Text { + content, + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: highlight_style, + attributes: Attributes::default(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(mark_node) +} diff --git a/code_markup/src/markup/convert/mod.rs b/code_markup/src/markup/convert/mod.rs new file mode 100644 index 0000000000..57b87c5918 --- /dev/null +++ b/code_markup/src/markup/convert/mod.rs @@ -0,0 +1,4 @@ +pub mod from_ast; +pub mod from_def2; +pub mod from_expr2; +pub mod from_header; diff --git a/code_markup/src/markup/mod.rs b/code_markup/src/markup/mod.rs index e3ce137f80..5b6dca5dfb 100644 --- a/code_markup/src/markup/mod.rs +++ b/code_markup/src/markup/mod.rs @@ -1,4 +1,5 @@ pub mod attribute; pub mod common_nodes; +pub mod convert; pub mod nodes; pub mod top_level_def; diff --git a/code_markup/src/markup/nodes.rs b/code_markup/src/markup/nodes.rs index 5243a9438b..f7c7ba3057 100644 --- a/code_markup/src/markup/nodes.rs +++ b/code_markup/src/markup/nodes.rs @@ -1,37 +1,17 @@ use crate::{ - markup::common_nodes::{ - new_blank_mn, new_colon_mn, new_comma_mn, new_equals_mn, new_left_accolade_mn, - new_left_square_mn, new_right_accolade_mn, new_right_square_mn, - }, markup_error::MarkResult, slow_pool::{MarkNodeId, SlowPool}, syntax_highlight::HighlightStyle, }; -use super::{ - attribute::Attributes, common_nodes::new_blank_mn_w_nls, top_level_def::tld_mark_node, -}; +use super::{attribute::Attributes, common_nodes::new_comma_mn_ast}; use crate::markup_error::{ExpectedTextNode, NestedNodeMissingChild, NestedNodeRequired}; +use itertools::Itertools; use roc_ast::{ - ast_error::ASTResult, - lang::{ - core::{ - ast::{ASTNodeId, AST}, - def::def2::{Def2, DefId}, - expr::{ - expr2::{Expr2, ExprId}, - record_field::RecordField, - }, - header::AppHeader, - pattern::get_identifier_string, - val_def::ValueDef, - }, - env::Env, - }, + lang::{core::ast::ASTNodeId, env::Env}, mem_pool::pool_str::PoolStr, }; -use roc_module::symbol::Interns; use roc_utils::{index_of, slice_get}; use std::fmt; @@ -54,10 +34,14 @@ pub enum MarkupNode { Blank { ast_node_id: ASTNodeId, attributes: Attributes, - syn_high_style: HighlightStyle, // TODO remove HighlightStyle, this is always HighlightStyle::Blank parent_id_opt: Option, newlines_at_end: usize, }, + Indent { + ast_node_id: ASTNodeId, + indent_level: usize, + parent_id_opt: Option, + }, } impl MarkupNode { @@ -66,6 +50,7 @@ impl MarkupNode { MarkupNode::Nested { ast_node_id, .. } => *ast_node_id, MarkupNode::Text { ast_node_id, .. } => *ast_node_id, MarkupNode::Blank { ast_node_id, .. } => *ast_node_id, + MarkupNode::Indent { ast_node_id, .. } => *ast_node_id, } } @@ -74,6 +59,7 @@ impl MarkupNode { MarkupNode::Nested { parent_id_opt, .. } => *parent_id_opt, MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt, MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt, + MarkupNode::Indent { parent_id_opt, .. } => *parent_id_opt, } } @@ -82,6 +68,7 @@ impl MarkupNode { MarkupNode::Nested { children_ids, .. } => children_ids.to_vec(), MarkupNode::Text { .. } => vec![], MarkupNode::Blank { .. } => vec![], + MarkupNode::Indent { .. } => vec![], } } @@ -176,6 +163,7 @@ impl MarkupNode { MarkupNode::Nested { .. } => "".to_owned(), MarkupNode::Text { content, .. } => content.clone(), MarkupNode::Blank { .. } => BLANK_PLACEHOLDER.to_owned(), + MarkupNode::Indent { indent_level, .. } => SINGLE_INDENT.repeat(*indent_level), } } @@ -192,13 +180,8 @@ impl MarkupNode { pub fn get_content_mut(&mut self) -> MarkResult<&mut String> { match self { - MarkupNode::Nested { .. } => ExpectedTextNode { - function_name: "set_content".to_owned(), - node_type: self.node_type_as_string(), - } - .fail(), MarkupNode::Text { content, .. } => Ok(content), - MarkupNode::Blank { .. } => ExpectedTextNode { + _ => ExpectedTextNode { function_name: "set_content".to_owned(), node_type: self.node_type_as_string(), } @@ -230,6 +213,7 @@ impl MarkupNode { MarkupNode::Nested { .. } => "Nested", MarkupNode::Text { .. } => "Text", MarkupNode::Blank { .. } => "Blank", + MarkupNode::Indent { .. } => "Indent", }; type_str.to_owned() @@ -254,6 +238,7 @@ impl MarkupNode { MarkupNode::Blank { newlines_at_end, .. } => *newlines_at_end, + MarkupNode::Indent { .. } => 0, } } @@ -268,11 +253,12 @@ impl MarkupNode { MarkupNode::Blank { newlines_at_end, .. } => *newlines_at_end += 1, + _ => {} } } } -fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String { +pub fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String { pool_str.as_str(env.pool).to_owned() } @@ -285,14 +271,17 @@ pub const COLON: &str = ": "; pub const COMMA: &str = ", "; pub const STRING_QUOTES: &str = "\"\""; pub const EQUALS: &str = " = "; +pub const ARROW: &str = " -> "; +pub const SINGLE_INDENT: &str = " "; // 4 spaces -fn new_markup_node( +pub fn new_markup_node( text: String, node_id: ASTNodeId, highlight_style: HighlightStyle, mark_node_pool: &mut SlowPool, + indent_level: usize, ) -> MarkNodeId { - let node = MarkupNode::Text { + let content_node = MarkupNode::Text { content: text, ast_node_id: node_id, syn_high_style: highlight_style, @@ -301,256 +290,28 @@ fn new_markup_node( newlines_at_end: 0, }; - mark_node_pool.add(node) -} + let content_node_id = mark_node_pool.add(content_node); -pub fn def2_to_markup<'a>( - env: &mut Env<'a>, - def2: &Def2, - def2_node_id: DefId, - mark_node_pool: &mut SlowPool, - interns: &Interns, -) -> ASTResult { - let ast_node_id = ASTNodeId::ADefId(def2_node_id); + if indent_level > 0 { + let indent_node = MarkupNode::Indent { + ast_node_id: node_id, + indent_level, + parent_id_opt: None, + }; - let mark_node_id = match def2 { - Def2::ValueDef { - identifier_id, - expr_id, - } => { - let expr_mn_id = expr2_to_markup( - env, - env.pool.get(*expr_id), - *expr_id, - mark_node_pool, - interns, - )?; + let indent_node_id = mark_node_pool.add(indent_node); - let tld_mn = tld_mark_node( - *identifier_id, - expr_mn_id, - ast_node_id, - mark_node_pool, - env, - interns, - )?; + let nested_node = MarkupNode::Nested { + ast_node_id: node_id, + children_ids: vec![indent_node_id, content_node_id], + parent_id_opt: None, + newlines_at_end: 0, + }; - mark_node_pool.add(tld_mn) - } - Def2::Blank => mark_node_pool.add(new_blank_mn_w_nls(ast_node_id, None, 2)), - }; - - Ok(mark_node_id) -} - -// make Markup Nodes: generate String representation, assign Highlighting Style -pub fn expr2_to_markup<'a>( - env: &mut Env<'a>, - expr2: &Expr2, - expr2_node_id: ExprId, - mark_node_pool: &mut SlowPool, - interns: &Interns, -) -> ASTResult { - let ast_node_id = ASTNodeId::AExprId(expr2_node_id); - - let mark_node_id = match expr2 { - Expr2::SmallInt { text, .. } - | Expr2::I128 { text, .. } - | Expr2::U128 { text, .. } - | Expr2::Float { text, .. } => { - let num_str = get_string(env, text); - - new_markup_node(num_str, ast_node_id, HighlightStyle::Number, mark_node_pool) - } - Expr2::Str(text) => new_markup_node( - "\"".to_owned() + text.as_str(env.pool) + "\"", - ast_node_id, - HighlightStyle::String, - mark_node_pool, - ), - Expr2::GlobalTag { name, .. } => new_markup_node( - get_string(env, name), - ast_node_id, - HighlightStyle::Type, - mark_node_pool, - ), - Expr2::Call { expr: expr_id, .. } => { - let expr = env.pool.get(*expr_id); - expr2_to_markup(env, expr, *expr_id, mark_node_pool, interns)? - } - Expr2::Var(symbol) => { - //TODO make bump_format with arena - let text = format!("{:?}", symbol); - new_markup_node(text, ast_node_id, HighlightStyle::Variable, mark_node_pool) - } - Expr2::List { elems, .. } => { - let mut children_ids = - vec![mark_node_pool.add(new_left_square_mn(expr2_node_id, None))]; - - let indexed_node_ids: Vec<(usize, ExprId)> = - elems.iter(env.pool).copied().enumerate().collect(); - - for (idx, node_id) in indexed_node_ids.iter() { - let sub_expr2 = env.pool.get(*node_id); - - children_ids.push(expr2_to_markup( - env, - sub_expr2, - *node_id, - mark_node_pool, - interns, - )?); - - if idx + 1 < elems.len() { - children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None))); - } - } - children_ids.push(mark_node_pool.add(new_right_square_mn(expr2_node_id, None))); - - let list_node = MarkupNode::Nested { - ast_node_id, - children_ids, - parent_id_opt: None, - newlines_at_end: 0, - }; - - mark_node_pool.add(list_node) - } - Expr2::EmptyRecord => { - let children_ids = vec![ - mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None)), - mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None)), - ]; - - let record_node = MarkupNode::Nested { - ast_node_id, - children_ids, - parent_id_opt: None, - newlines_at_end: 0, - }; - - mark_node_pool.add(record_node) - } - Expr2::Record { fields, .. } => { - let mut children_ids = - vec![mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None))]; - - for (idx, field_node_id) in fields.iter_node_ids().enumerate() { - let record_field = env.pool.get(field_node_id); - - let field_name = record_field.get_record_field_pool_str(); - - children_ids.push(new_markup_node( - field_name.as_str(env.pool).to_owned(), - ast_node_id, - HighlightStyle::RecordField, - mark_node_pool, - )); - - match record_field { - RecordField::InvalidLabelOnly(_, _) => (), - RecordField::LabelOnly(_, _, _) => (), - RecordField::LabeledValue(_, _, sub_expr2_node_id) => { - children_ids.push(mark_node_pool.add(new_colon_mn(expr2_node_id, None))); - - let sub_expr2 = env.pool.get(*sub_expr2_node_id); - children_ids.push(expr2_to_markup( - env, - sub_expr2, - *sub_expr2_node_id, - mark_node_pool, - interns, - )?); - } - } - - if idx + 1 < fields.len() { - children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None))); - } - } - - children_ids.push(mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None))); - - let record_node = MarkupNode::Nested { - ast_node_id, - children_ids, - parent_id_opt: None, - newlines_at_end: 0, - }; - - mark_node_pool.add(record_node) - } - Expr2::Blank => mark_node_pool.add(new_blank_mn(ast_node_id, None)), - Expr2::LetValue { - def_id, - body_id: _, - body_var: _, - } => { - let pattern_id = env.pool.get(*def_id).get_pattern_id(); - - let pattern2 = env.pool.get(pattern_id); - - let val_name = get_identifier_string(pattern2, interns)?; - - let val_name_mn = MarkupNode::Text { - content: val_name, - ast_node_id, - syn_high_style: HighlightStyle::Variable, - attributes: Attributes::default(), - parent_id_opt: None, - newlines_at_end: 0, - }; - - let val_name_mn_id = mark_node_pool.add(val_name_mn); - - let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None)); - - let value_def = env.pool.get(*def_id); - - match value_def { - ValueDef::NoAnnotation { - pattern_id: _, - expr_id, - expr_var: _, - } => { - let body_mn_id = expr2_to_markup( - env, - env.pool.get(*expr_id), - *expr_id, - mark_node_pool, - interns, - )?; - - let body_mn = mark_node_pool.get_mut(body_mn_id); - body_mn.add_newline_at_end(); - - let full_let_node = MarkupNode::Nested { - ast_node_id, - children_ids: vec![val_name_mn_id, equals_mn_id, body_mn_id], - parent_id_opt: None, - newlines_at_end: 1, - }; - - mark_node_pool.add(full_let_node) - } - other => { - unimplemented!( - "I don't know how to convert {:?} into a MarkupNode yet.", - other - ) - } - } - } - Expr2::RuntimeError() => new_markup_node( - "RunTimeError".to_string(), - ast_node_id, - HighlightStyle::Blank, - mark_node_pool, - ), - rest => todo!("implement expr2_to_markup for {:?}", rest), - }; - - Ok(mark_node_id) + mark_node_pool.add(nested_node) + } else { + content_node_id + } } pub fn set_parent_for_all(markup_node_id: MarkNodeId, mark_node_pool: &mut SlowPool) { @@ -596,221 +357,10 @@ pub fn set_parent_for_all_helper( } MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id), MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id), + MarkupNode::Indent { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id), } } -fn header_mn(content: String, expr_id: ExprId, mark_node_pool: &mut SlowPool) -> MarkNodeId { - let mark_node = MarkupNode::Text { - content, - ast_node_id: ASTNodeId::AExprId(expr_id), - syn_high_style: HighlightStyle::PackageRelated, - attributes: Attributes::default(), - parent_id_opt: None, - newlines_at_end: 0, - }; - - mark_node_pool.add(mark_node) -} - -fn header_val_mn( - content: String, - expr_id: ExprId, - highlight_style: HighlightStyle, - mark_node_pool: &mut SlowPool, -) -> MarkNodeId { - let mark_node = MarkupNode::Text { - content, - ast_node_id: ASTNodeId::AExprId(expr_id), - syn_high_style: highlight_style, - attributes: Attributes::default(), - parent_id_opt: None, - newlines_at_end: 0, - }; - - mark_node_pool.add(mark_node) -} - -pub fn header_to_markup(app_header: &AppHeader, mark_node_pool: &mut SlowPool) -> MarkNodeId { - let expr_id = app_header.ast_node_id; - let ast_node_id = ASTNodeId::AExprId(expr_id); - - let app_node_id = header_mn("app ".to_owned(), expr_id, mark_node_pool); - - let app_name_node_id = header_val_mn( - app_header.app_name.clone(), - expr_id, - HighlightStyle::String, - mark_node_pool, - ); - - let full_app_node = MarkupNode::Nested { - ast_node_id, - children_ids: vec![app_node_id, app_name_node_id], - parent_id_opt: None, - newlines_at_end: 1, - }; - - let packages_node_id = header_mn(" packages ".to_owned(), expr_id, mark_node_pool); - - let pack_left_acc_node_id = mark_node_pool.add(new_left_accolade_mn(expr_id, None)); - - let pack_base_node_id = header_val_mn( - "base: ".to_owned(), - expr_id, - HighlightStyle::RecordField, - mark_node_pool, - ); - - let pack_val_node_id = header_val_mn( - app_header.packages_base.clone(), - expr_id, - HighlightStyle::String, - mark_node_pool, - ); - - let pack_right_acc_node_id = mark_node_pool.add(new_right_accolade_mn(expr_id, None)); - - let full_packages_node = MarkupNode::Nested { - ast_node_id, - children_ids: vec![ - packages_node_id, - pack_left_acc_node_id, - pack_base_node_id, - pack_val_node_id, - pack_right_acc_node_id, - ], - parent_id_opt: None, - newlines_at_end: 1, - }; - - let imports_node_id = header_mn(" imports ".to_owned(), expr_id, mark_node_pool); - - let imports_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None)); - - let mut import_child_ids: Vec = add_header_mn_list( - &app_header.imports, - expr_id, - HighlightStyle::Import, - mark_node_pool, - ); - - let imports_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None)); - - let mut full_import_children = vec![imports_node_id, imports_left_square_node_id]; - - full_import_children.append(&mut import_child_ids); - full_import_children.push(imports_right_square_node_id); - - let full_import_node = MarkupNode::Nested { - ast_node_id, - children_ids: full_import_children, - parent_id_opt: None, - newlines_at_end: 1, - }; - - let provides_node_id = header_mn(" provides ".to_owned(), expr_id, mark_node_pool); - - let provides_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None)); - - let mut provides_val_node_ids: Vec = add_header_mn_list( - &app_header.provides, - expr_id, - HighlightStyle::Provides, - mark_node_pool, - ); - - let provides_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None)); - - let provides_end_node_id = header_mn(" to base".to_owned(), expr_id, mark_node_pool); - - let mut full_provides_children = vec![provides_node_id, provides_left_square_node_id]; - - full_provides_children.append(&mut provides_val_node_ids); - full_provides_children.push(provides_right_square_node_id); - full_provides_children.push(provides_end_node_id); - - let full_provides_node = MarkupNode::Nested { - ast_node_id, - children_ids: full_provides_children, - parent_id_opt: None, - newlines_at_end: 1, - }; - - let full_app_node_id = mark_node_pool.add(full_app_node); - let full_packages_node = mark_node_pool.add(full_packages_node); - let full_import_node_id = mark_node_pool.add(full_import_node); - let full_provides_node_id = mark_node_pool.add(full_provides_node); - - let header_mark_node = MarkupNode::Nested { - ast_node_id, - children_ids: vec![ - full_app_node_id, - full_packages_node, - full_import_node_id, - full_provides_node_id, - ], - parent_id_opt: None, - newlines_at_end: 1, - }; - - let header_mn_id = mark_node_pool.add(header_mark_node); - - set_parent_for_all(header_mn_id, mark_node_pool); - - header_mn_id -} - -// Used for provides and imports -fn add_header_mn_list( - str_vec: &[String], - expr_id: ExprId, - highlight_style: HighlightStyle, - mark_node_pool: &mut SlowPool, -) -> Vec { - let nr_of_elts = str_vec.len(); - - str_vec - .iter() - .enumerate() - .map(|(indx, provide_str)| { - let provide_str = header_val_mn( - provide_str.to_owned(), - expr_id, - highlight_style, - mark_node_pool, - ); - - if indx != nr_of_elts - 1 { - vec![provide_str, mark_node_pool.add(new_comma_mn(expr_id, None))] - } else { - vec![provide_str] - } - }) - .flatten() - .collect() -} - -pub fn ast_to_mark_nodes<'a>( - env: &mut Env<'a>, - ast: &AST, - mark_node_pool: &mut SlowPool, - interns: &Interns, -) -> ASTResult> { - let mut all_mark_node_ids = vec![header_to_markup(&ast.header, mark_node_pool)]; - - for &def_id in ast.def_ids.iter() { - let def2 = env.pool.get(def_id); - - let def2_markup_id = def2_to_markup(env, def2, def_id, mark_node_pool, interns)?; - - set_parent_for_all(def2_markup_id, mark_node_pool); - - all_mark_node_ids.push(def2_markup_id); - } - - Ok(all_mark_node_ids) -} - impl fmt::Display for MarkupNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( @@ -871,3 +421,50 @@ pub fn get_root_mark_node_id(mark_node_id: MarkNodeId, mark_node_pool: &SlowPool curr_mark_node_id } + +// put space mark nodes between each node in mark_nodes +pub fn join_mark_nodes_spaces( + mark_nodes_ids: Vec, + with_prepend: bool, + ast_node_id: ASTNodeId, + mark_node_pool: &mut SlowPool, +) -> Vec { + let space_range_max = if with_prepend { + mark_nodes_ids.len() + } else { + mark_nodes_ids.len() - 1 + }; + + let join_nodes: Vec = (0..space_range_max) + .map(|_| { + let space_node = MarkupNode::Text { + content: " ".to_string(), + ast_node_id, + syn_high_style: HighlightStyle::Blank, + attributes: Attributes::default(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(space_node) + }) + .collect(); + + if with_prepend { + join_nodes.into_iter().interleave(mark_nodes_ids).collect() + } else { + mark_nodes_ids.into_iter().interleave(join_nodes).collect() + } +} + +// put comma mark nodes between each node in mark_nodes +pub fn join_mark_nodes_commas( + mark_nodes: Vec, + ast_node_id: ASTNodeId, +) -> Vec { + let join_nodes: Vec = (0..(mark_nodes.len() - 1)) + .map(|_| new_comma_mn_ast(ast_node_id, None)) + .collect(); + + mark_nodes.into_iter().interleave(join_nodes).collect() +} diff --git a/code_markup/src/markup/top_level_def.rs b/code_markup/src/markup/top_level_def.rs index d2122fc790..996e40299f 100644 --- a/code_markup/src/markup/top_level_def.rs +++ b/code_markup/src/markup/top_level_def.rs @@ -1,14 +1,8 @@ use roc_ast::{ ast_error::ASTResult, - lang::{ - core::{ - ast::ASTNodeId, - pattern::{get_identifier_string, PatternId}, - }, - env::Env, - }, + lang::{core::ast::ASTNodeId, env::Env}, }; -use roc_module::symbol::Interns; +use roc_module::symbol::IdentId; use crate::{ markup::{attribute::Attributes, common_nodes::new_equals_mn, nodes::MarkupNode}, @@ -17,20 +11,18 @@ use crate::{ }; pub fn tld_mark_node<'a>( - identifier_id: PatternId, + identifier_id: IdentId, expr_mark_node_id: MarkNodeId, ast_node_id: ASTNodeId, mark_node_pool: &mut SlowPool, env: &Env<'a>, - interns: &Interns, ) -> ASTResult { - let pattern2 = env.pool.get(identifier_id); - let val_name = get_identifier_string(pattern2, interns)?; + let val_name = env.ident_ids.get_name_str_res(identifier_id)?; let val_name_mn = MarkupNode::Text { - content: val_name, + content: val_name.to_owned(), ast_node_id, - syn_high_style: HighlightStyle::Variable, + syn_high_style: HighlightStyle::Value, attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, diff --git a/code_markup/src/markup_error.rs b/code_markup/src/markup_error.rs index 5de9768fba..c1843547a1 100644 --- a/code_markup/src/markup_error.rs +++ b/code_markup/src/markup_error.rs @@ -38,7 +38,7 @@ pub enum MarkError { node_type: String, backtrace: Backtrace, }, - #[snafu(display("UIError: {}", msg))] + #[snafu(display("UtilError: {}", msg))] UtilErrorBacktrace { msg: String, backtrace: Backtrace }, } diff --git a/code_markup/src/syntax_highlight.rs b/code_markup/src/syntax_highlight.rs index dfdb32076d..094d331590 100644 --- a/code_markup/src/syntax_highlight.rs +++ b/code_markup/src/syntax_highlight.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use crate::colors::{self, from_hsb, RgbaTup}; +use crate::colors::{from_hsb, RgbaTup}; #[derive(Hash, Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)] pub enum HighlightStyle { @@ -9,11 +9,12 @@ pub enum HighlightStyle { Comma, String, FunctionName, + FunctionArgName, Type, Bracket, Number, PackageRelated, // app, packages, imports, exposes, provides... - Variable, + Value, RecordField, Import, Provides, @@ -23,20 +24,23 @@ pub enum HighlightStyle { pub fn default_highlight_map() -> HashMap { use HighlightStyle::*; + let almost_white = from_hsb(258, 5, 95); + let mut highlight_map = HashMap::new(); [ - (Operator, colors::WHITE), + (Operator, from_hsb(185, 50, 75)), (Comma, from_hsb(258, 50, 90)), (String, from_hsb(346, 65, 97)), - (FunctionName, colors::WHITE), - (Type, colors::WHITE), + (FunctionName, almost_white), + (FunctionArgName, from_hsb(225, 50, 100)), + (Type, almost_white), (Bracket, from_hsb(347, 80, 100)), - (Number, from_hsb(185, 50, 75)), - (PackageRelated, colors::WHITE), - (Variable, colors::WHITE), + (Number, from_hsb(225, 50, 100)), + (PackageRelated, almost_white), + (Value, almost_white), (RecordField, from_hsb(258, 50, 90)), - (Import, from_hsb(185, 50, 75)), - (Provides, from_hsb(185, 50, 75)), + (Import, from_hsb(225, 50, 100)), + (Provides, from_hsb(225, 50, 100)), (Blank, from_hsb(258, 50, 90)), // comment from_hsb(285, 6, 47) or 186, 35, 40 ] 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 1658ced629..182730da7f 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; @@ -300,6 +300,40 @@ pub fn build_c_host_native( command.output().unwrap() } +pub fn build_swift_host_native( + env_path: &str, + env_home: &str, + dest: &str, + sources: &[&str], + opt_level: OptLevel, + shared_lib_path: Option<&Path>, + objc_header_path: Option<&str>, +) -> Output { + if shared_lib_path.is_some() { + unimplemented!("Linking a shared library to Swift not yet implemented"); + } + + let mut command = Command::new("swiftc"); + command + .env_clear() + .env("PATH", &env_path) + .env("HOME", &env_home) + .args(sources) + .arg("-emit-object") + .arg("-parse-as-library") + .args(&["-o", dest]); + + if let Some(objc_header) = objc_header_path { + command.args(&["-import-objc-header", objc_header]); + } + + if matches!(opt_level, OptLevel::Optimize) { + command.arg("-O"); + } + + command.output().unwrap() +} + pub fn rebuild_host( opt_level: OptLevel, target: &Triple, @@ -312,6 +346,9 @@ pub fn rebuild_host( let rust_host_src = host_input_path.with_file_name("host.rs"); let rust_host_dest = host_input_path.with_file_name("rust_host.o"); let cargo_host_src = host_input_path.with_file_name("Cargo.toml"); + let swift_host_src = host_input_path.with_file_name("host.swift"); + let swift_host_header_src = host_input_path.with_file_name("host.h"); + let host_dest_native = host_input_path.with_file_name(if shared_lib_path.is_some() { "dynhost" } else { @@ -372,6 +409,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), }; @@ -526,18 +577,25 @@ pub fn rebuild_host( shared_lib_path, ); validate_output("host.c", "clang", output); + } else if swift_host_src.exists() { + // Compile host.swift, if it exists + let output = build_swift_host_native( + &env_path, + &env_home, + host_dest_native.to_str().unwrap(), + &[swift_host_src.to_str().unwrap()], + opt_level, + shared_lib_path, + swift_host_header_src + .exists() + .then(|| swift_host_header_src.to_str().unwrap()), + ); + validate_output("host.swift", "swiftc", output); } } -fn nixos_path() -> String { - env::var("NIXOS_GLIBC_PATH").unwrap_or_else(|_| { - panic!( - "We couldn't find glibc! We tried looking for NIXOS_GLIBC_PATH -to find it via Nix, but that didn't work either. Please file a bug report. - -This will only be an issue until we implement surgical linking.", - ) - }) +fn nix_path_opt() -> Option { + env::var_os("NIX_GLIBC_PATH").map(|path| path.into_string().unwrap()) } fn library_path(segments: [&str; N]) -> Option { @@ -586,21 +644,39 @@ fn link_linux( )); } - let libcrt_path = library_path(["/usr", "lib", &architecture]) - .or_else(|| library_path(["/usr", "lib"])) - .or_else(|| library_path([&nixos_path()])) - .unwrap(); + let libcrt_path = + // give preference to nix_path if it's defined, this prevents bugs + if let Some(nix_path) = nix_path_opt() { + library_path([&nix_path]) + .unwrap() + } else { + library_path(["/usr", "lib", &architecture]) + .or_else(|| library_path(["/usr", "lib"])) + .unwrap() + }; let libgcc_name = "libgcc_s.so.1"; - let libgcc_path = library_path(["/lib", &architecture, libgcc_name]) - .or_else(|| library_path(["/usr", "lib", &architecture, libgcc_name])) - .or_else(|| library_path(["/usr", "lib", libgcc_name])) - .or_else(|| library_path([&nixos_path(), libgcc_name])) - .unwrap(); + let libgcc_path = + // give preference to nix_path if it's defined, this prevents bugs + if let Some(nix_path) = nix_path_opt() { + library_path([&nix_path, libgcc_name]) + .unwrap() + } else { + library_path(["/lib", &architecture, libgcc_name]) + .or_else(|| library_path(["/usr", "lib", &architecture, libgcc_name])) + .or_else(|| library_path(["/usr", "lib", libgcc_name])) + .unwrap() + }; let ld_linux = match target.architecture { - Architecture::X86_64 => library_path(["/lib64", "ld-linux-x86-64.so.2"]) - .or_else(|| library_path([&nixos_path(), "ld-linux-x86-64.so.2"])), + Architecture::X86_64 => { + // give preference to nix_path if it's defined, this prevents bugs + if let Some(nix_path) = nix_path_opt() { + library_path([&nix_path, "ld-linux-x86-64.so.2"]) + } else { + library_path(["/lib64", "ld-linux-x86-64.so.2"]) + } + } Architecture::Aarch64(_) => library_path(["/lib", "ld-linux-aarch64.so.1"]), _ => panic!( "TODO gracefully handle unsupported linux architecture: {:?}", @@ -665,7 +741,7 @@ fn link_linux( .args(&[ "--gc-sections", "--eh-frame-hdr", - "-arch", + "-A", arch_str(target), "-pie", libcrt_path.join("crti.o").to_str().unwrap(), @@ -674,7 +750,7 @@ fn link_linux( .args(&base_args) .args(&["-dynamic-linker", ld_linux]) .args(input_paths) - // ld.lld requires this argument, and does not accept -arch + // ld.lld requires this argument, and does not accept --arch // .args(&["-L/usr/lib/x86_64-linux-gnu"]) .args(&[ // Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496365925 @@ -713,22 +789,14 @@ fn link_macos( } }; - // This path only exists on macOS Big Sur, and it causes ld errors - // on Catalina if it's specified with -L, so we replace it with a - // redundant -lSystem if the directory isn't there. - let big_sur_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib"; - let big_sur_fix = if Path::new(big_sur_path).exists() { - format!("-L{}", big_sur_path) - } else { - String::from("-lSystem") - }; - let arch = match target.architecture { Architecture::Aarch64(_) => "arm64".to_string(), _ => target.architecture.to_string(), }; - let mut ld_child = Command::new("ld") + let mut ld_command = Command::new("ld"); + + ld_command // NOTE: order of arguments to `ld` matters here! // The `-l` flags should go after the `.o` arguments // Don't allow LD_ env vars to affect this @@ -743,33 +811,40 @@ fn link_macos( "-arch", &arch, ]) - .args(input_paths) - .args(&[ - // Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274 - // for discussion and further references - &big_sur_fix, - "-lSystem", - "-lresolv", - "-lpthread", - // "-lrt", // TODO shouldn't we need this? - // "-lc_nonshared", // TODO shouldn't we need this? - // "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840 - // "-framework", // Uncomment this line & the following ro run the `rand` crate in examples/cli - // "Security", - // Output - "-o", - output_path.to_str().unwrap(), // app - ]) - .spawn()?; + .args(input_paths); + + let sdk_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib"; + if Path::new(sdk_path).exists() { + ld_command.arg(format!("-L{}", sdk_path)); + ld_command.arg(format!("-L{}/swift", sdk_path)); + }; + + ld_command.args(&[ + // Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274 + // for discussion and further references + "-lSystem", + "-lresolv", + "-lpthread", + // "-lrt", // TODO shouldn't we need this? + // "-lc_nonshared", // TODO shouldn't we need this? + // "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840 + // "-framework", // Uncomment this line & the following ro run the `rand` crate in examples/cli + // "Security", + // Output + "-o", + output_path.to_str().unwrap(), // app + ]); + + let mut ld_child = ld_command.spawn()?; match target.architecture { Architecture::Aarch64(_) => { ld_child.wait()?; - let child = Command::new("codesign") + let codesign_child = Command::new("codesign") .args(&["-s", "-", output_path.to_str().unwrap()]) .spawn()?; - Ok((child, output_path)) + Ok((codesign_child, output_path)) } _ => Ok((ld_child, output_path)), } 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/build.zig b/compiler/builtins/bitcode/build.zig index 3f1ede59b5..e5927a022f 100644 --- a/compiler/builtins/bitcode/build.zig +++ b/compiler/builtins/bitcode/build.zig @@ -86,8 +86,8 @@ pub fn build(b: *Builder) void { fn removeInstallSteps(b: *Builder) void { for (b.top_level_steps.items) |top_level_step, i| { - if (mem.eql(u8, top_level_step.step.name, "install") or mem.eql(u8, top_level_step.step.name, "uninstall")) { - const name = top_level_step.step.name; + const name = top_level_step.step.name; + if (mem.eql(u8, name, "install") or mem.eql(u8, name, "uninstall")) { _ = b.top_level_steps.swapRemove(i); } } 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 2eea0b6464..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"); @@ -121,6 +122,7 @@ comptime { exportStrFn(str.fromUtf8C, "from_utf8"); exportStrFn(str.fromUtf8RangeC, "from_utf8_range"); exportStrFn(str.repeat, "repeat"); + exportStrFn(str.strTrim, "trim"); } // Utils @@ -159,7 +161,8 @@ fn exportUtilsFn(comptime func: anytype, comptime func_name: []const u8) void { // Custom panic function, as builtin Zig version errors during LLVM verification pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn { - if (std.builtin.is_test) { + const builtin = @import("builtin"); + if (builtin.is_test) { std.debug.print("{s}: {?}", .{ message, stacktrace }); } else { _ = message; diff --git a/compiler/builtins/bitcode/src/str.zig b/compiler/builtins/bitcode/src/str.zig index 9ffb9a1375..7ca72c7cc4 100644 --- a/compiler/builtins/bitcode/src/str.zig +++ b/compiler/builtins/bitcode/src/str.zig @@ -93,7 +93,7 @@ pub const RocStr = extern struct { if (length < roc_str_size) { return RocStr.empty(); } else { - var new_bytes: []T = utils.alloc(length, RocStr.alignment) catch unreachable; + var new_bytes = utils.alloc(length, RocStr.alignment) catch unreachable; var new_bytes_ptr: [*]u8 = @ptrCast([*]u8, &new_bytes); @@ -163,7 +163,7 @@ pub const RocStr = extern struct { ) RocStr { const element_width = 1; - if (self.bytes) |source_ptr| { + if (self.str_bytes) |source_ptr| { if (self.isUnique()) { const new_source = utils.unsafeReallocate(source_ptr, RocStr.alignment, self.len(), new_length, element_width); @@ -171,7 +171,7 @@ pub const RocStr = extern struct { } } - return self.reallocateFresh(RocStr.alignment, new_length, element_width); + return self.reallocateFresh(new_length); } /// reallocate by explicitly making a new allocation and copying elements over @@ -294,7 +294,7 @@ pub const RocStr = extern struct { } pub fn isUnique(self: RocStr) bool { - // the empty list is unique (in the sense that copying it will not leak memory) + // the empty string is unique (in the sense that copying it will not leak memory) if (self.isEmpty()) { return true; } @@ -305,6 +305,10 @@ pub const RocStr = extern struct { } // otherwise, check if the refcount is one + return @call(.{ .modifier = always_inline }, RocStr.isRefcountOne, .{self}); + } + + fn isRefcountOne(self: RocStr) bool { const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, self.str_bytes)); return (ptr - 1)[0] == utils.REFCOUNT_ONE; } @@ -1473,3 +1477,253 @@ test "validateUtf8Bytes: surrogate halves" { try expectErr(list, 3, error.Utf8EncodesSurrogateHalf, Utf8ByteProblem.EncodesSurrogateHalf); } + +fn isWhitespace(codepoint: u21) bool { + // https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt + return switch (codepoint) { + 0x0009...0x000D => true, // control characters + 0x0020 => true, // space + 0x0085 => true, // control character + 0x00A0 => true, // no-break space + 0x1680 => true, // ogham space + 0x2000...0x200A => true, // en quad..hair space + 0x200E...0x200F => true, // left-to-right & right-to-left marks + 0x2028 => true, // line separator + 0x2029 => true, // paragraph separator + 0x202F => true, // narrow no-break space + 0x205F => true, // medium mathematical space + 0x3000 => true, // ideographic space + + else => false, + }; +} + +test "isWhitespace" { + try expect(isWhitespace(' ')); + try expect(isWhitespace('\u{00A0}')); + try expect(!isWhitespace('x')); +} + +pub fn strTrim(string: RocStr) callconv(.C) RocStr { + if (string.str_bytes) |bytes_ptr| { + const leading_bytes = countLeadingWhitespaceBytes(string); + const original_len = string.len(); + + if (original_len == leading_bytes) { + string.deinit(); + return RocStr.empty(); + } + + const trailing_bytes = countTrailingWhitespaceBytes(string); + const new_len = original_len - leading_bytes - trailing_bytes; + + const small_or_shared = new_len <= SMALL_STR_MAX_LENGTH or !string.isRefcountOne(); + if (small_or_shared) { + return RocStr.init(string.asU8ptr() + leading_bytes, new_len); + } + + // nonempty, large, and unique: + + if (leading_bytes > 0) { + var i: usize = 0; + while (i < new_len) : (i += 1) { + const dest = bytes_ptr + i; + const source = dest + leading_bytes; + @memcpy(dest, source, 1); + } + } + + var new_string = string; + new_string.str_len = new_len; + + return new_string; + } + + return RocStr.empty(); +} + +fn countLeadingWhitespaceBytes(string: RocStr) usize { + var byte_count: usize = 0; + + var bytes = string.asU8ptr()[0..string.len()]; + var iter = unicode.Utf8View.initUnchecked(bytes).iterator(); + while (iter.nextCodepoint()) |codepoint| { + if (isWhitespace(codepoint)) { + byte_count += unicode.utf8CodepointSequenceLength(codepoint) catch break; + } else { + break; + } + } + + return byte_count; +} + +fn countTrailingWhitespaceBytes(string: RocStr) usize { + var byte_count: usize = 0; + + var bytes = string.asU8ptr()[0..string.len()]; + var iter = ReverseUtf8View.initUnchecked(bytes).iterator(); + while (iter.nextCodepoint()) |codepoint| { + if (isWhitespace(codepoint)) { + byte_count += unicode.utf8CodepointSequenceLength(codepoint) catch break; + } else { + break; + } + } + + return byte_count; +} + +/// A backwards version of Utf8View from std.unicode +const ReverseUtf8View = struct { + bytes: []const u8, + + pub fn initUnchecked(s: []const u8) ReverseUtf8View { + return ReverseUtf8View{ .bytes = s }; + } + + pub fn iterator(s: ReverseUtf8View) ReverseUtf8Iterator { + return ReverseUtf8Iterator{ + .bytes = s.bytes, + .i = if (s.bytes.len > 0) s.bytes.len - 1 else null, + }; + } +}; + +/// A backwards version of Utf8Iterator from std.unicode +const ReverseUtf8Iterator = struct { + bytes: []const u8, + // NOTE null signifies complete/empty + i: ?usize, + + pub fn nextCodepointSlice(it: *ReverseUtf8Iterator) ?[]const u8 { + if (it.i) |index| { + var i = index; + + // NOTE this relies on the string being valid utf8 to not run off the end + while (!utf8BeginByte(it.bytes[i])) { + i -= 1; + } + + const cp_len = unicode.utf8ByteSequenceLength(it.bytes[i]) catch unreachable; + const slice = it.bytes[i .. i + cp_len]; + + it.i = if (i == 0) null else i - 1; + + return slice; + } else { + return null; + } + } + + pub fn nextCodepoint(it: *ReverseUtf8Iterator) ?u21 { + const slice = it.nextCodepointSlice() orelse return null; + + return switch (slice.len) { + 1 => @as(u21, slice[0]), + 2 => unicode.utf8Decode2(slice) catch unreachable, + 3 => unicode.utf8Decode3(slice) catch unreachable, + 4 => unicode.utf8Decode4(slice) catch unreachable, + else => unreachable, + }; + } +}; + +fn utf8BeginByte(byte: u8) bool { + return switch (byte) { + 0b1000_0000...0b1011_1111 => false, + else => true, + }; +} + +test "strTrim: empty" { + const trimmedEmpty = strTrim(RocStr.empty()); + try expect(trimmedEmpty.eq(RocStr.empty())); +} + +test "strTrim: blank" { + const original_bytes = " "; + const original = RocStr.init(original_bytes, original_bytes.len); + defer original.deinit(); + + const trimmed = strTrim(original); + + try expect(trimmed.eq(RocStr.empty())); +} + +test "strTrim: large to large" { + const original_bytes = " hello giant world "; + const original = RocStr.init(original_bytes, original_bytes.len); + defer original.deinit(); + + try expect(!original.isSmallStr()); + + const expected_bytes = "hello giant world"; + const expected = RocStr.init(expected_bytes, expected_bytes.len); + defer expected.deinit(); + + try expect(!expected.isSmallStr()); + + const trimmed = strTrim(original); + + try expect(trimmed.eq(expected)); +} + +test "strTrim: large to small" { + const original_bytes = " hello world "; + const original = RocStr.init(original_bytes, original_bytes.len); + defer original.deinit(); + + try expect(!original.isSmallStr()); + + const expected_bytes = "hello world"; + const expected = RocStr.init(expected_bytes, expected_bytes.len); + defer expected.deinit(); + + try expect(expected.isSmallStr()); + + const trimmed = strTrim(original); + + try expect(trimmed.eq(expected)); + try expect(trimmed.isSmallStr()); +} + +test "strTrim: small to small" { + const original_bytes = " hello world "; + const original = RocStr.init(original_bytes, original_bytes.len); + defer original.deinit(); + + try expect(original.isSmallStr()); + + const expected_bytes = "hello world"; + const expected = RocStr.init(expected_bytes, expected_bytes.len); + defer expected.deinit(); + + try expect(expected.isSmallStr()); + + const trimmed = strTrim(original); + + try expect(trimmed.eq(expected)); + try expect(trimmed.isSmallStr()); +} + +test "ReverseUtf8View: hello world" { + const original_bytes = "hello world"; + const expected_bytes = "dlrow olleh"; + + var i: usize = 0; + var iter = ReverseUtf8View.initUnchecked(original_bytes).iterator(); + while (iter.nextCodepoint()) |codepoint| { + try expect(expected_bytes[i] == codepoint); + i += 1; + } +} + +test "ReverseUtf8View: empty" { + const original_bytes = ""; + + var iter = ReverseUtf8View.initUnchecked(original_bytes).iterator(); + while (iter.nextCodepoint()) |codepoint| { + try expect(false); + } +} diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index 1371afe674..8626205271 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -19,8 +19,9 @@ extern fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void; extern fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void; comptime { + const builtin = @import("builtin"); // During tetsts, use the testing allocators to satisfy these functions. - if (std.builtin.is_test) { + if (builtin.is_test) { @export(testing_roc_alloc, .{ .name = "roc_alloc", .linkage = .Strong }); @export(testing_roc_realloc, .{ .name = "roc_realloc", .linkage = .Strong }); @export(testing_roc_dealloc, .{ .name = "roc_dealloc", .linkage = .Strong }); @@ -257,7 +258,7 @@ pub const Ordering = enum(u8) { LT = 2, }; -pub const UpdateMode = extern enum(u8) { +pub const UpdateMode = enum(u8) { Immutable = 0, InPlace = 1, }; diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index c8f5363fd1..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, @@ -34,6 +35,7 @@ interface List sortWith, drop, dropAt, + dropLast, swap ] imports [] @@ -268,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 @@ -439,6 +446,9 @@ drop : List elem, Nat -> List elem ## To replace the element at a given index, instead of dropping it, see [List.set]. dropAt : List elem, Nat -> List elem +## Drops the last element in a List. +dropLast : List elem -> List elem + ## Adds a new element to the end of the list. ## ## >>> List.append [ "a", "b" ] "c" diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 2623b2ad3b..1876c8100c 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -142,6 +142,7 @@ pub const STR_TO_UTF8: &str = "roc_builtins.str.to_utf8"; pub const STR_FROM_UTF8: &str = "roc_builtins.str.from_utf8"; pub const STR_FROM_UTF8_RANGE: &str = "roc_builtins.str.from_utf8_range"; pub const STR_REPEAT: &str = "roc_builtins.str.repeat"; +pub const STR_TRIM: &str = "roc_builtins.str.trim"; pub const DICT_HASH: &str = "roc_builtins.dict.hash"; pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str"; @@ -164,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 f17cc22098..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()); @@ -632,6 +632,9 @@ pub fn types() -> MutMap { Box::new(str_type()) ); + // trim : Str -> Str + add_top_level_function_type!(Symbol::STR_TRIM, vec![str_type()], Box::new(str_type())); + // fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]* { let bad_utf8 = SolvedType::TagUnion( @@ -722,7 +725,7 @@ pub fn types() -> MutMap { add_top_level_function_type!( Symbol::LIST_LAST, vec![list_type(flex(TVAR1))], - Box::new(result_type(flex(TVAR1), list_was_empty)), + Box::new(result_type(flex(TVAR1), list_was_empty.clone())), ); // set : List elem, Nat, elem -> List elem @@ -746,6 +749,20 @@ pub fn types() -> MutMap { Box::new(bool_type()), ); + // min : List (Num a) -> Result (Num a) [ ListWasEmpty ]* + add_top_level_function_type!( + Symbol::LIST_MIN, + vec![list_type(num_type(flex(TVAR1)))], + Box::new(result_type(num_type(flex(TVAR1)), list_was_empty.clone())), + ); + + // max : List (Num a) -> Result (Num a) [ ListWasEmpty ]* + add_top_level_function_type!( + Symbol::LIST_MAX, + vec![list_type(num_type(flex(TVAR1)))], + Box::new(result_type(num_type(flex(TVAR1)), list_was_empty)), + ); + // sum : List (Num a) -> Num a add_top_level_function_type!( Symbol::LIST_SUM, @@ -913,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, @@ -934,6 +972,13 @@ pub fn types() -> MutMap { Box::new(list_type(flex(TVAR1))), ); + // dropLast : List elem -> List elem + add_top_level_function_type!( + Symbol::LIST_DROP_LAST, + vec![list_type(flex(TVAR1))], + Box::new(list_type(flex(TVAR1))), + ); + // swap : List elem, Nat, Nat -> List elem add_top_level_function_type!( Symbol::LIST_SWAP, 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 3546fa6549..fb55cf56fa 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1,5 +1,5 @@ use crate::def::Def; -use crate::expr::Expr::*; +use crate::expr::{ClosureData, Expr::*}; use crate::expr::{Expr, Recursive, WhenBranch}; use crate::pattern::Pattern; use roc_collections::all::SendMap; @@ -67,6 +67,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option STR_TO_UTF8 => str_to_utf8, STR_FROM_FLOAT=> str_from_float, STR_REPEAT => str_repeat, + STR_TRIM => str_trim, LIST_LEN => list_len, LIST_GET => list_get, LIST_SET => list_set, @@ -79,6 +80,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option LIST_REVERSE => list_reverse, LIST_CONCAT => list_concat, LIST_CONTAINS => list_contains, + LIST_MIN => list_min, + LIST_MAX => list_max, LIST_SUM => list_sum, LIST_PRODUCT => list_product, LIST_PREPEND => list_prepend, @@ -86,8 +89,10 @@ 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, LIST_SWAP => list_swap, LIST_MAP_WITH_INDEX => list_map_with_index, LIST_KEEP_IF => list_keep_if, @@ -286,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(); @@ -1236,6 +1276,11 @@ fn str_split(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// Str.trim : Str -> Str +fn str_trim(symbol: Symbol, var_store: &mut VarStore) -> Def { + lowlevel_1(symbol, LowLevel::StrTrim, var_store) +} + /// Str.repeat : Str, Nat -> Str fn str_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def { let str_var = var_store.fresh(); @@ -2004,6 +2049,51 @@ fn list_drop_at(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// List.dropLast: List elem -> List elem +fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def { + let list_var = var_store.fresh(); + let index_var = var_store.fresh(); + let arg_var = var_store.fresh(); + let len_var = Variable::NAT; + let num_var = len_var; + let num_precision_var = Variable::NATURAL; + + let body = RunLowLevel { + op: LowLevel::ListDropAt, + args: vec![ + (list_var, Var(Symbol::ARG_1)), + ( + index_var, + // Num.sub (List.len list) 1 + RunLowLevel { + op: LowLevel::NumSubWrap, + args: vec![ + ( + arg_var, + // List.len list + RunLowLevel { + op: LowLevel::ListLen, + args: vec![(list_var, Var(Symbol::ARG_1))], + ret_var: len_var, + }, + ), + (arg_var, int(num_var, num_precision_var, 1)), + ], + ret_var: len_var, + }, + ), + ], + ret_var: list_var, + }; + + defn( + symbol, + vec![(list_var, Symbol::ARG_1)], + var_store, + body, + list_var, + ) +} /// List.append : List elem, elem -> List elem fn list_append(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); @@ -2085,6 +2175,266 @@ fn list_walk_until(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_3(symbol, LowLevel::ListWalkUntil, var_store) } +// min : List (Num a) -> Result (Num a) [ ListWasEmpty ]* +fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def { + let arg_var = var_store.fresh(); + let bool_var = var_store.fresh(); + let list_var = var_store.fresh(); + let len_var = Variable::NAT; + let num_var = len_var; + let num_precision_var = Variable::NATURAL; + let list_elem_var = var_store.fresh(); + let ret_var = var_store.fresh(); + let closure_var = var_store.fresh(); + + // Perform a bounds check. If it passes, delegate to List.getUnsafe. + let body = If { + cond_var: bool_var, + branch_var: var_store.fresh(), + branches: vec![( + // if-condition + no_region( + // List.len list != 0 + RunLowLevel { + op: LowLevel::NotEq, + args: vec![ + (len_var, int(num_var, num_precision_var, 0)), + ( + len_var, + RunLowLevel { + op: LowLevel::ListLen, + args: vec![(list_var, Var(Symbol::ARG_1))], + ret_var: len_var, + }, + ), + ], + ret_var: bool_var, + }, + ), + // list was not empty + no_region( + // Ok ( List.walk list (List.getUnsafe list 0) \acc,elem -> if elem < acc then elem else acc ) + tag( + "Ok", + vec![ + // List.walk list (List.getUnsafe list 0) \acc,elem -> if elem < acc then elem else acc + RunLowLevel { + op: LowLevel::ListWalk, + args: vec![ + (list_var, Var(Symbol::ARG_1)), + // (List.getUnsafe list 0) + ( + list_elem_var, + RunLowLevel { + op: LowLevel::ListGetUnsafe, + args: vec![ + (list_var, Var(Symbol::ARG_1)), + (arg_var, int(num_var, num_precision_var, 0)), + ], + ret_var: list_elem_var, + }, + ), + // \acc,elem -> if elem < acc then elem else acc + (closure_var, list_min_lt(list_elem_var, var_store)), + ], + ret_var: list_elem_var, + }, + ], + var_store, + ), + ), + )], + final_else: Box::new( + // list was empty + no_region( + // Err ListWasEmpty + tag( + "Err", + vec![tag("ListWasEmpty", Vec::new(), var_store)], + var_store, + ), + ), + ), + }; + + defn( + symbol, + vec![(list_var, Symbol::ARG_1)], + var_store, + body, + ret_var, + ) +} + +// \acc,elem -> if elem < acc then elem else acc +fn list_min_lt(list_elem_var: Variable, var_store: &mut VarStore) -> Expr { + let bool_var = var_store.fresh(); + // if elem < acc then elem else acc + let body = If { + cond_var: bool_var, + branch_var: list_elem_var, + branches: vec![( + // if-condition + no_region( + // elem < acc + RunLowLevel { + op: LowLevel::NumLt, + args: vec![ + (list_elem_var, Var(Symbol::ARG_4)), + (list_elem_var, Var(Symbol::ARG_3)), + ], + ret_var: bool_var, + }, + ), + // return elem + no_region(Var(Symbol::ARG_4)), + )], + // return acc + final_else: Box::new(no_region(Var(Symbol::ARG_3))), + }; + + defn_help( + Symbol::LIST_MIN_LT, + vec![ + (list_elem_var, Symbol::ARG_3), + (list_elem_var, Symbol::ARG_4), + ], + var_store, + body, + list_elem_var, + ) +} + +// max : List (Num a) -> Result (Num a) [ ListWasEmpty ]* +fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def { + let arg_var = var_store.fresh(); + let bool_var = var_store.fresh(); + let list_var = var_store.fresh(); + let len_var = Variable::NAT; + let num_var = len_var; + let num_precision_var = Variable::NATURAL; + let list_elem_var = var_store.fresh(); + let ret_var = var_store.fresh(); + let closure_var = var_store.fresh(); + + // Perform a bounds check. If it passes, delegate to List.getUnsafe. + let body = If { + cond_var: bool_var, + branch_var: var_store.fresh(), + branches: vec![( + // if-condition + no_region( + // List.len list != 0 + RunLowLevel { + op: LowLevel::NotEq, + args: vec![ + (len_var, int(num_var, num_precision_var, 0)), + ( + len_var, + RunLowLevel { + op: LowLevel::ListLen, + args: vec![(list_var, Var(Symbol::ARG_1))], + ret_var: len_var, + }, + ), + ], + ret_var: bool_var, + }, + ), + // list was not empty + no_region( + // Ok ( List.walk list (List.getUnsafe list 0) \acc,elem -> if elem < acc then elem else acc ) + tag( + "Ok", + vec![ + // List.walk list (List.getUnsafe list 0) \acc,elem -> if elem < acc then elem else acc + RunLowLevel { + op: LowLevel::ListWalk, + args: vec![ + (list_var, Var(Symbol::ARG_1)), + // (List.getUnsafe list 0) + ( + list_elem_var, + RunLowLevel { + op: LowLevel::ListGetUnsafe, + args: vec![ + (list_var, Var(Symbol::ARG_1)), + (arg_var, int(num_var, num_precision_var, 0)), + ], + ret_var: list_elem_var, + }, + ), + // \acc,elem -> if elem < acc then elem else acc + (closure_var, list_max_gt(list_elem_var, var_store)), + ], + ret_var: list_elem_var, + }, + ], + var_store, + ), + ), + )], + final_else: Box::new( + // list was empty + no_region( + // Err ListWasEmpty + tag( + "Err", + vec![tag("ListWasEmpty", Vec::new(), var_store)], + var_store, + ), + ), + ), + }; + + defn( + symbol, + vec![(list_var, Symbol::ARG_1)], + var_store, + body, + ret_var, + ) +} + +// \acc,elem -> if elem > acc then elem else acc +fn list_max_gt(list_elem_var: Variable, var_store: &mut VarStore) -> Expr { + let bool_var = var_store.fresh(); + // if elem > acc then elem else acc + let body = If { + cond_var: bool_var, + branch_var: list_elem_var, + branches: vec![( + // if-condition + no_region( + // elem > acc + RunLowLevel { + op: LowLevel::NumGt, + args: vec![ + (list_elem_var, Var(Symbol::ARG_4)), + (list_elem_var, Var(Symbol::ARG_3)), + ], + ret_var: bool_var, + }, + ), + // return elem + no_region(Var(Symbol::ARG_4)), + )], + // return acc + final_else: Box::new(no_region(Var(Symbol::ARG_3))), + }; + + defn_help( + Symbol::LIST_MAX_GT, + vec![ + (list_elem_var, Symbol::ARG_3), + (list_elem_var, Symbol::ARG_4), + ], + var_store, + body, + list_elem_var, + ) +} + /// List.sum : List (Num a) -> Num a fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def { let num_var = var_store.fresh(); @@ -2227,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) @@ -2575,7 +2930,7 @@ fn set_walk(symbol: Symbol, var_store: &mut VarStore) -> Def { CalledVia::Space, ); - let wrapper = Closure { + let wrapper = Closure(ClosureData { function_type: wrapper_var, closure_type: var_store.fresh(), closure_ext_var: var_store.fresh(), @@ -2589,7 +2944,7 @@ fn set_walk(symbol: Symbol, var_store: &mut VarStore) -> Def { (Variable::EMPTY_RECORD, no_region(Pattern::Underscore)), ], loc_body: Box::new(no_region(call_func)), - }; + }); let body = RunLowLevel { op: LowLevel::DictWalk, @@ -3604,7 +3959,7 @@ fn defn_help( .map(|(var, symbol)| (var, no_region(Identifier(symbol)))) .collect(); - Closure { + Closure(ClosureData { function_type: var_store.fresh(), closure_type: var_store.fresh(), closure_ext_var: var_store.fresh(), @@ -3614,7 +3969,7 @@ fn defn_help( recursive: Recursive::NotRecursive, arguments: closure_args, loc_body: Box::new(no_region(body)), - } + }) } #[inline(always)] diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index e5e3414048..a44e83efe0 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1,6 +1,7 @@ use crate::annotation::canonicalize_annotation; use crate::annotation::IntroducedVariables; use crate::env::Env; +use crate::expr::ClosureData; use crate::expr::Expr::{self, *}; use crate::expr::{ canonicalize_expr, local_successors, references_from_call, references_from_local, Output, @@ -670,10 +671,10 @@ fn group_to_declaration( let mut new_def = can_def.clone(); // Determine recursivity of closures that are not tail-recursive - if let Closure { + if let Closure(ClosureData { recursive: recursive @ Recursive::NotRecursive, .. - } = &mut new_def.loc_expr.value + }) = &mut new_def.loc_expr.value { *recursive = closure_recursivity(*symbol, closures); } @@ -698,10 +699,10 @@ fn group_to_declaration( let mut new_def = can_def.clone(); // Determine recursivity of closures that are not tail-recursive - if let Closure { + if let Closure(ClosureData { recursive: recursive @ Recursive::NotRecursive, .. - } = &mut new_def.loc_expr.value + }) = &mut new_def.loc_expr.value { *recursive = closure_recursivity(symbol, closures); } @@ -838,7 +839,7 @@ fn canonicalize_pending_def<'a>( }; Located { - value: Closure { + value: Closure(ClosureData { function_type: var_store.fresh(), closure_type: var_store.fresh(), closure_ext_var: var_store.fresh(), @@ -848,7 +849,7 @@ fn canonicalize_pending_def<'a>( recursive: Recursive::NotRecursive, arguments: underscores, loc_body: Box::new(body_expr), - }, + }), region: loc_ann.region, } }; @@ -1001,7 +1002,7 @@ fn canonicalize_pending_def<'a>( if let ( &ast::Pattern::Identifier(_name), &Pattern::Identifier(ref defined_symbol), - &Closure { + &Closure(ClosureData { function_type, closure_type, closure_ext_var, @@ -1011,7 +1012,7 @@ fn canonicalize_pending_def<'a>( loc_body: ref body, ref captured_symbols, .. - }, + }), ) = ( &loc_pattern.value, &loc_can_pattern.value, @@ -1049,7 +1050,7 @@ fn canonicalize_pending_def<'a>( }); // renamed_closure_def = Some(&defined_symbol); - loc_can_expr.value = Closure { + loc_can_expr.value = Closure(ClosureData { function_type, closure_type, closure_ext_var, @@ -1059,7 +1060,7 @@ fn canonicalize_pending_def<'a>( recursive: is_recursive, arguments: arguments.clone(), loc_body: body.clone(), - }; + }); } // Store the referenced locals in the refs_by_symbol map, so we can later figure out @@ -1144,7 +1145,7 @@ fn canonicalize_pending_def<'a>( if let ( &ast::Pattern::Identifier(_name), &Pattern::Identifier(ref defined_symbol), - &Closure { + &Closure(ClosureData { function_type, closure_type, closure_ext_var, @@ -1154,7 +1155,7 @@ fn canonicalize_pending_def<'a>( loc_body: ref body, ref captured_symbols, .. - }, + }), ) = ( &loc_pattern.value, &loc_can_pattern.value, @@ -1191,7 +1192,7 @@ fn canonicalize_pending_def<'a>( refs.lookups = refs.lookups.without(defined_symbol); }); - loc_can_expr.value = Closure { + loc_can_expr.value = Closure(ClosureData { function_type, closure_type, closure_ext_var, @@ -1201,7 +1202,7 @@ fn canonicalize_pending_def<'a>( recursive: is_recursive, arguments: arguments.clone(), loc_body: body.clone(), - }; + }); } // Store the referenced locals in the refs_by_symbol map, so we can later figure out diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 5dd8903993..79d762560e 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -102,17 +102,7 @@ pub enum Expr { ret_var: Variable, }, - Closure { - function_type: Variable, - closure_type: Variable, - closure_ext_var: Variable, - return_type: Variable, - name: Symbol, - captured_symbols: Vec<(Symbol, Variable)>, - recursive: Recursive, - arguments: Vec<(Variable, Located)>, - loc_body: Box>, - }, + Closure(ClosureData), // Product Types Record { @@ -173,6 +163,18 @@ pub enum Expr { // Compiles, but will crash if reached RuntimeError(RuntimeError), } +#[derive(Clone, Debug, PartialEq)] +pub struct ClosureData { + pub function_type: Variable, + pub closure_type: Variable, + pub closure_ext_var: Variable, + pub return_type: Variable, + pub name: Symbol, + pub captured_symbols: Vec<(Symbol, Variable)>, + pub recursive: Recursive, + pub arguments: Vec<(Variable, Located)>, + pub loc_body: Box>, +} #[derive(Clone, Debug, PartialEq)] pub struct Field { @@ -572,7 +574,7 @@ pub fn canonicalize_expr<'a>( } ( - Closure { + Closure(ClosureData { function_type: var_store.fresh(), closure_type: var_store.fresh(), closure_ext_var: var_store.fresh(), @@ -582,7 +584,7 @@ pub fn canonicalize_expr<'a>( recursive: Recursive::NotRecursive, arguments: can_args, loc_body: Box::new(loc_body_expr), - }, + }), output, ) } @@ -1403,7 +1405,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> LetNonRec(Box::new(def), Box::new(loc_expr), var) } - Closure { + Closure(ClosureData { function_type, closure_type, closure_ext_var, @@ -1413,14 +1415,14 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> captured_symbols, arguments, loc_body, - } => { + }) => { let loc_expr = *loc_body; let loc_expr = Located { value: inline_calls(var_store, scope, loc_expr.value), region: loc_expr.region, }; - Closure { + Closure(ClosureData { function_type, closure_type, closure_ext_var, @@ -1430,7 +1432,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> captured_symbols, arguments, loc_body: Box::new(loc_expr), - } + }) } Record { record_var, fields } => { @@ -1492,12 +1494,12 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> loc_expr: Located { value: - Closure { + Closure(ClosureData { recursive, arguments: params, loc_body: boxed_body, .. - }, + }), .. }, .. diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 771f73611d..f8c6928e56 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -1,6 +1,6 @@ use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def}; use crate::env::Env; -use crate::expr::{Expr, Output}; +use crate::expr::{ClosureData, Expr, Output}; use crate::operator::desugar_def; use crate::pattern::Pattern; use crate::scope::Scope; @@ -416,13 +416,13 @@ fn fix_values_captured_in_closure_expr( fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols); } - Closure { + Closure(ClosureData { captured_symbols, name, arguments, loc_body, .. - } => { + }) => { captured_symbols.retain(|(s, _)| !no_capture_symbols.contains(s)); captured_symbols.retain(|(s, _)| s != name); diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index 51dd68b8ac..335750c14d 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -15,7 +15,7 @@ mod test_can { use crate::helpers::{can_expr_with, test_home, CanExprOut}; use bumpalo::Bump; use roc_can::expr::Expr::{self, *}; - use roc_can::expr::Recursive; + use roc_can::expr::{ClosureData, Recursive}; use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError}; use roc_region::all::Region; use std::{f64, i64}; @@ -654,10 +654,10 @@ mod test_can { match expr { LetRec(assignments, body, _) => { match &assignments.get(i).map(|def| &def.loc_expr.value) { - Some(Closure { + Some(Closure(ClosureData { recursive: recursion, .. - }) => recursion.clone(), + })) => recursion.clone(), Some(other) => { panic!("assignment at {} is not a closure, but a {:?}", i, other) } @@ -676,10 +676,10 @@ mod test_can { get_closure(&body.value, i - 1) } else { match &def.loc_expr.value { - Closure { + Closure(ClosureData { recursive: recursion, .. - } => recursion.clone(), + }) => recursion.clone(), other => { panic!("assignment at {} is not a closure, but a {:?}", i, other) } 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/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 4e920147d0..11d8c4cabe 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -7,7 +7,7 @@ use roc_can::def::{Declaration, Def}; use roc_can::expected::Expected::{self, *}; use roc_can::expected::PExpected; use roc_can::expr::Expr::{self, *}; -use roc_can::expr::{Field, WhenBranch}; +use roc_can::expr::{ClosureData, Field, WhenBranch}; use roc_can::pattern::Pattern; use roc_collections::all::{ImMap, Index, MutSet, SendMap}; use roc_module::ident::{Lowercase, TagName}; @@ -333,7 +333,7 @@ pub fn constrain_expr( // make lookup constraint to lookup this symbol's type in the environment Lookup(*symbol, expected, region) } - Closure { + Closure(ClosureData { function_type: fn_var, closure_type: closure_var, closure_ext_var, @@ -343,7 +343,7 @@ pub fn constrain_expr( captured_symbols, name, .. - } => { + }) => { // NOTE defs are treated somewhere else! let loc_body_expr = &**boxed; @@ -1203,7 +1203,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { // instead of the more generic "something is wrong with the body of `f`" match (&def.loc_expr.value, &signature) { ( - Closure { + Closure(ClosureData { function_type: fn_var, closure_type: closure_var, closure_ext_var, @@ -1213,7 +1213,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { loc_body, name, .. - }, + }), Type::Function(arg_types, signature_closure_type, ret_type), ) => { // NOTE if we ever have problems with the closure, the ignored `_closure_type` @@ -1561,7 +1561,7 @@ pub fn rec_defs_help( // instead of the more generic "something is wrong with the body of `f`" match (&def.loc_expr.value, &signature) { ( - Closure { + Closure(ClosureData { function_type: fn_var, closure_type: closure_var, closure_ext_var, @@ -1571,7 +1571,7 @@ pub fn rec_defs_help( loc_body, name, .. - }, + }), Type::Function(arg_types, _closure_type, ret_type), ) => { // NOTE if we ever have trouble with closure type unification, the ignored 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 44c3565104..ed17333c28 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -10,14 +10,14 @@ 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, str_from_utf8, str_from_utf8_range, str_join_with, str_number_of_bytes, str_repeat, str_split, - str_starts_with, str_starts_with_code_point, str_to_utf8, + str_starts_with, str_starts_with_code_point, str_to_utf8, str_trim, }; use crate::llvm::compare::{generic_eq, generic_neq}; use crate::llvm::convert::{ @@ -1470,6 +1470,26 @@ pub fn build_tag<'a, 'ctx, 'env>( .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; let mut field_types = Vec::with_capacity_in(num_fields, env.arena); @@ -2687,14 +2707,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>, @@ -3898,7 +3910,7 @@ fn build_procedures_help<'a, 'ctx, 'env>( let it = procedures.iter().map(|x| x.1); - let solutions = match roc_mono::alias_analysis::spec_program(entry_point, it) { + let solutions = match roc_mono::alias_analysis::spec_program(opt_level, entry_point, it) { Err(e) => panic!("Error in alias analysis: {}", e), Ok(solutions) => solutions, }; @@ -4715,6 +4727,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); @@ -5059,6 +5132,12 @@ fn run_low_level<'a, 'ctx, 'env>( str_count_graphemes(env, scope, args[0]) } + StrTrim => { + // Str.trim : Str -> Str + debug_assert_eq!(args.len(), 1); + + str_trim(env, scope, args[0]) + } ListLen => { // List.len : List * -> Int debug_assert_eq!(args.len(), 1); @@ -5740,7 +5819,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_llvm/src/llvm/build_str.rs b/compiler/gen_llvm/src/llvm/build_str.rs index 564f35625e..8a8241719b 100644 --- a/compiler/gen_llvm/src/llvm/build_str.rs +++ b/compiler/gen_llvm/src/llvm/build_str.rs @@ -249,6 +249,16 @@ pub fn str_count_graphemes<'a, 'ctx, 'env>( ) } +/// Str.trim : Str -> Str +pub fn str_trim<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + scope: &Scope<'a, 'ctx>, + str_symbol: Symbol, +) -> BasicValueEnum<'ctx> { + let str_i128 = str_symbol_to_c_abi(env, scope, str_symbol); + call_bitcode_fn(env, &[str_i128.into()], bitcode::STR_TRIM) +} + /// Str.fromInt : Int -> Str pub fn str_from_int<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, diff --git a/compiler/gen_wasm/Cargo.toml b/compiler/gen_wasm/Cargo.toml index 2804be6eab..501303a2e5 100644 --- a/compiler/gen_wasm/Cargo.toml +++ b/compiler/gen_wasm/Cargo.toml @@ -10,11 +10,9 @@ roc_collections = { path = "../collections" } roc_module = { path = "../module" } roc_mono = { path = "../mono" } bumpalo = { version = "3.6.1", features = ["collections"] } -parity-wasm = "0.42" roc_std = { path = "../../roc_std" } wasmer = "2.0.0" -wasmer-wasi = "2.0.0" [dev-dependencies] roc_can = { path = "../can" } @@ -24,6 +22,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/README.md b/compiler/gen_wasm/README.md index 46c7e12a15..4a9469c445 100644 --- a/compiler/gen_wasm/README.md +++ b/compiler/gen_wasm/README.md @@ -16,7 +16,7 @@ - [x] Distinguish which variables go in locals, own stack frame, caller stack frame, etc. - [x] Ensure early Return statements don't skip stack cleanup - [x] Model the stack machine as a storage mechanism, to make generated code "less bad" - - [ ] Vendor-in parity_wasm library so that we can use `bumpalo::Vec` + - [x] Switch vectors to `bumpalo::Vec` where possible - [ ] Implement relocations - Requires knowing the _byte_ offset of each call site. This is awkward as the backend builds a `Vec` rather than a `Vec`. It may be worth serialising each instruction as it is inserted. @@ -225,6 +225,4 @@ The Module is a _specification_ for how to create an Instance of the program. Th A WebAssembly module is equivalent to an executable file. It doesn't normally need relocations since at the WebAssembly layer, there is no Address Space Layout Randomisation. If it has relocations then it's an object file. -The [official spec](https://webassembly.github.io/spec/core/binary/modules.html#sections) lists the sections that are part of the final module. It doesn't mention any sections for relocations or symbol names, but it has room for "custom sections" that in practice seem to be used for that. - -The WebAssembly `tool-conventions` repo has a document on [linking](https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md), and the `parity_wasm` crate supports "name" and "relocation" [sections](https://docs.rs/parity-wasm/0.42.2/parity_wasm/elements/enum.Section.html). +The [official spec](https://webassembly.github.io/spec/core/binary/modules.html#sections) lists the sections that are part of the final module. It doesn't mention any sections for relocations or symbol names, but it does support "custom" sections. Conventions to use those for linking are documented in the WebAssembly `tool-conventions` repo [here](https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md) and it mentions that LLVM is using those conventions. diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 85f22635f4..6d22ada08f 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -1,8 +1,4 @@ -use parity_wasm::builder; -use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder, SignatureBuilder}; -use parity_wasm::elements::{ - BlockType, Instruction, Instruction::*, Instructions, Local, ValueType, -}; +use bumpalo::collections::Vec; use roc_collections::all::MutMap; use roc_module::low_level::LowLevel; @@ -10,10 +6,10 @@ use roc_module::symbol::Symbol; use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::layout::{Builtin, Layout}; -use crate::code_builder::CodeBuilder; use crate::layout::WasmLayout; use crate::storage::{Storage, StoredValue, StoredValueKind}; -use crate::{copy_memory, pop_stack_frame, push_stack_frame, CopyMemoryConfig, LocalId, PTR_TYPE}; +use crate::wasm_module::{BlockType, CodeBuilder, LocalId, Signature, ValueType, WasmModule}; +use crate::{copy_memory, CopyMemoryConfig, Env, 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) @@ -22,47 +18,50 @@ const UNUSED_DATA_SECTION_BYTES: u32 = 1024; #[derive(Clone, Copy, Debug)] struct LabelId(u32); -// TODO: use Bumpalo Vec once parity_wasm supports general iterators (>=0.43) pub struct WasmBackend<'a> { - // Module level: Wasm AST - pub module_builder: ModuleBuilder, + env: &'a Env<'a>, - // Module level: internal state & IR mappings + // Module-level data + pub module: WasmModule<'a>, _data_offset_map: MutMap, u32>, _data_offset_next: u32, - proc_symbol_map: MutMap, + proc_symbols: Vec<'a, Symbol>, - // Function level - code_builder: CodeBuilder, - storage: Storage, + // Function-level data + code_builder: CodeBuilder<'a>, + storage: Storage<'a>, /// how many blocks deep are we (used for jumps) block_depth: u32, - joinpoint_label_map: MutMap)>, + joinpoint_label_map: MutMap)>, } impl<'a> WasmBackend<'a> { - pub fn new() -> Self { + pub fn new(env: &'a Env<'a>, proc_symbols: Vec<'a, Symbol>) -> Self { WasmBackend { - // Module: Wasm AST - module_builder: builder::module(), + env, - // Module: internal state & IR mappings + // Module-level data + module: WasmModule::new(env.arena), _data_offset_map: MutMap::default(), _data_offset_next: UNUSED_DATA_SECTION_BYTES, - proc_symbol_map: MutMap::default(), + proc_symbols, + // Function-level data block_depth: 0, joinpoint_label_map: MutMap::default(), - - // Functions - code_builder: CodeBuilder::new(), - storage: Storage::new(), + code_builder: CodeBuilder::new(env.arena), + storage: Storage::new(env.arena), } } + /// Reset function-level data fn reset(&mut self) { - self.code_builder.clear(); + // Push the completed CodeBuilder into the module and swap it for a new empty one + let mut swap_code_builder = CodeBuilder::new(self.env.arena); + std::mem::swap(&mut swap_code_builder, &mut self.code_builder); + self.module.code.code_builders.push(swap_code_builder); + self.storage.clear(); self.joinpoint_label_map.clear(); assert_eq!(self.block_depth, 0); @@ -74,98 +73,57 @@ impl<'a> WasmBackend<'a> { ***********************************************************/ - pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result { - // println!("\ngenerating procedure {:?}\n", sym); + pub fn build_proc(&mut self, proc: Proc<'a>, _sym: Symbol) -> Result<(), String> { + // println!("\ngenerating procedure {:?}\n", _sym); - let signature_builder = self.start_proc(&proc); + self.start_proc(&proc); self.build_stmt(&proc.body, &proc.ret_layout)?; - let function_def = self.finalize_proc(signature_builder); - let location = self.module_builder.push_function(function_def); - let function_index = location.body; - self.proc_symbol_map.insert(sym, location); + self.finalize_proc()?; self.reset(); - // println!("\nfinished generating {:?}\n", sym); - Ok(function_index) + // println!("\nfinished generating {:?}\n", _sym); + + Ok(()) } - fn start_proc(&mut self, proc: &Proc<'a>) -> SignatureBuilder { + fn start_proc(&mut self, proc: &Proc<'a>) { let ret_layout = WasmLayout::new(&proc.ret_layout); - - let signature_builder = if let WasmLayout::StackMemory { .. } = ret_layout { + let ret_type = if ret_layout.is_stack_memory() { self.storage.arg_types.push(PTR_TYPE); self.start_block(BlockType::NoResult); // block to ensure all paths pop stack memory (if any) - builder::signature() + None } else { - let ret_type = ret_layout.value_type(); - self.start_block(BlockType::Value(ret_type)); // block to ensure all paths pop stack memory (if any) - builder::signature().with_result(ret_type) + let ty = ret_layout.value_type(); + self.start_block(BlockType::Value(ty)); // block to ensure all paths pop stack memory (if any) + Some(ty) }; for (layout, symbol) in proc.args { - self.storage.allocate( - &WasmLayout::new(layout), - *symbol, - StoredValueKind::Parameter, - ); + let arg_layout = WasmLayout::new(layout); + self.storage + .allocate(&arg_layout, *symbol, StoredValueKind::Parameter); } - signature_builder.with_params(self.storage.arg_types.clone()) + self.module.add_function_signature(Signature { + param_types: self.storage.arg_types.clone(), + ret_type, + }); } - fn finalize_proc(&mut self, signature_builder: SignatureBuilder) -> FunctionDefinition { - self.end_block(); // end the block from start_proc, to ensure all paths pop stack memory (if any) + fn finalize_proc(&mut self) -> Result<(), String> { + // end the block from start_proc, to ensure all paths pop stack memory (if any) + self.end_block(); - const STACK_FRAME_INSTRUCTIONS_LEN: usize = 10; - let mut final_instructions = - Vec::with_capacity(self.code_builder.len() + STACK_FRAME_INSTRUCTIONS_LEN); + // Write local declarations and stack frame push/pop code + self.code_builder.finalize( + &self.storage.local_types, + self.storage.stack_frame_size, + self.storage.stack_frame_pointer, + ); - if self.storage.stack_frame_size > 0 { - push_stack_frame( - &mut final_instructions, - self.storage.stack_frame_size, - self.storage.stack_frame_pointer.unwrap(), - ); - } - - self.code_builder.finalize_into(&mut final_instructions); - - if self.storage.stack_frame_size > 0 { - pop_stack_frame( - &mut final_instructions, - self.storage.stack_frame_size, - self.storage.stack_frame_pointer.unwrap(), - ); - } - final_instructions.push(End); - - // Declare local variables (in batches of the same type) - let num_locals = self.storage.local_types.len(); - let mut locals = Vec::with_capacity(num_locals); - if num_locals > 0 { - let mut batch_type = self.storage.local_types[0]; - let mut batch_size = 0; - for t in &self.storage.local_types { - if *t == batch_type { - batch_size += 1; - } else { - locals.push(Local::new(batch_size, batch_type)); - batch_type = *t; - batch_size = 1; - } - } - locals.push(Local::new(batch_size, batch_type)); - } - - builder::function() - .with_signature(signature_builder.build_sig()) - .body() - .with_locals(locals) - .with_instructions(Instructions::new(final_instructions)) - .build() // body - .build() // function + Ok(()) } /********************************************************** @@ -177,17 +135,17 @@ impl<'a> WasmBackend<'a> { /// start a loop that leaves a value on the stack fn start_loop_with_return(&mut self, value_type: ValueType) { self.block_depth += 1; - self.code_builder.push(Loop(BlockType::Value(value_type))); + self.code_builder.loop_(BlockType::Value(value_type)); } fn start_block(&mut self, block_type: BlockType) { self.block_depth += 1; - self.code_builder.push(Block(block_type)); + self.code_builder.block(block_type); } fn end_block(&mut self) { self.block_depth -= 1; - self.code_builder.push(End); + self.code_builder.end(); } fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> { @@ -250,7 +208,7 @@ impl<'a> WasmBackend<'a> { _ => { self.storage.load_symbols(&mut self.code_builder, &[*sym]); - self.code_builder.push(Br(self.block_depth)); // jump to end of function (for stack frame pop) + self.code_builder.br(self.block_depth); // jump to end of function (for stack frame pop) } } @@ -288,13 +246,13 @@ impl<'a> WasmBackend<'a> { self.storage .load_symbols(&mut self.code_builder, &[*cond_symbol]); - self.code_builder.push(I32Const(*value as i32)); + self.code_builder.i32_const(*value as i32); // compare the 2 topmost values - self.code_builder.push(I32Eq); + self.code_builder.i32_eq(); // "break" out of `i` surrounding blocks - self.code_builder.push(BrIf(i as u32)); + self.code_builder.br_if(i as u32); } // if we never jumped because a value matched, we're in the default case @@ -318,7 +276,7 @@ impl<'a> WasmBackend<'a> { remainder, } => { // make locals for join pointer parameters - let mut jp_param_storages = std::vec::Vec::with_capacity(parameters.len()); + let mut jp_param_storages = Vec::with_capacity_in(parameters.len(), self.env.arena); for parameter in parameters.iter() { let wasm_layout = WasmLayout::new(¶meter.layout); let mut param_storage = self.storage.allocate( @@ -370,7 +328,7 @@ impl<'a> WasmBackend<'a> { // jump let levels = self.block_depth - target; - self.code_builder.push(Br(levels)); + self.code_builder.br(levels); Ok(()) } @@ -406,7 +364,8 @@ impl<'a> WasmBackend<'a> { let mut wasm_args_tmp: Vec; let (wasm_args, has_return_val) = match wasm_layout { WasmLayout::StackMemory { .. } => { - wasm_args_tmp = Vec::with_capacity(arguments.len() + 1); // TODO: bumpalo + wasm_args_tmp = + Vec::with_capacity_in(arguments.len() + 1, self.env.arena); wasm_args_tmp.push(*sym); wasm_args_tmp.extend_from_slice(*arguments); (wasm_args_tmp.as_slice(), false) @@ -416,16 +375,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 + )); + } + }; - self.code_builder.push_call( - function_location.body, + // 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, ); + Ok(()) } @@ -442,25 +414,25 @@ impl<'a> WasmBackend<'a> { } fn load_literal(&mut self, lit: &Literal<'a>, layout: &Layout<'a>) -> Result<(), String> { - let instruction = match lit { - Literal::Bool(x) => I32Const(*x as i32), - Literal::Byte(x) => I32Const(*x as i32), + match lit { + Literal::Bool(x) => self.code_builder.i32_const(*x as i32), + Literal::Byte(x) => self.code_builder.i32_const(*x as i32), Literal::Int(x) => match layout { - Layout::Builtin(Builtin::Int64) => I64Const(*x as i64), + Layout::Builtin(Builtin::Int64) => self.code_builder.i64_const(*x as i64), Layout::Builtin( Builtin::Int32 | Builtin::Int16 | Builtin::Int8 | Builtin::Int1 | Builtin::Usize, - ) => I32Const(*x as i32), + ) => self.code_builder.i32_const(*x as i32), x => { return Err(format!("loading literal, {:?}, is not yet implemented", x)); } }, Literal::Float(x) => match layout { - Layout::Builtin(Builtin::Float64) => F64Const((*x as f64).to_bits()), - Layout::Builtin(Builtin::Float32) => F32Const((*x as f32).to_bits()), + Layout::Builtin(Builtin::Float64) => self.code_builder.f64_const(*x as f64), + Layout::Builtin(Builtin::Float32) => self.code_builder.f32_const(*x as f32), x => { return Err(format!("loading literal, {:?}, is not yet implemented", x)); } @@ -469,7 +441,6 @@ impl<'a> WasmBackend<'a> { return Err(format!("loading literal, {:?}, is not yet implemented", x)); } }; - self.code_builder.push(instruction); Ok(()) } @@ -540,34 +511,33 @@ impl<'a> WasmBackend<'a> { // Some Roc low-level ops care about wrapping, clipping, sign-extending... // For those, we'll need to pre-process each argument before the main op, // so simple arrays of instructions won't work. But there are common patterns. - let instructions: &[Instruction] = match lowlevel { + match lowlevel { LowLevel::NumAdd => match return_value_type { - ValueType::I32 => &[I32Add], - ValueType::I64 => &[I64Add], - ValueType::F32 => &[F32Add], - ValueType::F64 => &[F64Add], + ValueType::I32 => self.code_builder.i32_add(), + ValueType::I64 => self.code_builder.i64_add(), + ValueType::F32 => self.code_builder.f32_add(), + ValueType::F64 => self.code_builder.f64_add(), }, LowLevel::NumSub => match return_value_type { - ValueType::I32 => &[I32Sub], - ValueType::I64 => &[I64Sub], - ValueType::F32 => &[F32Sub], - ValueType::F64 => &[F64Sub], + ValueType::I32 => self.code_builder.i32_sub(), + ValueType::I64 => self.code_builder.i64_sub(), + ValueType::F32 => self.code_builder.f32_sub(), + ValueType::F64 => self.code_builder.f64_sub(), }, LowLevel::NumMul => match return_value_type { - ValueType::I32 => &[I32Mul], - ValueType::I64 => &[I64Mul], - ValueType::F32 => &[F32Mul], - ValueType::F64 => &[F64Mul], + ValueType::I32 => self.code_builder.i32_mul(), + ValueType::I64 => self.code_builder.i64_mul(), + ValueType::F32 => self.code_builder.f32_mul(), + ValueType::F64 => self.code_builder.f64_mul(), }, LowLevel::NumGt => { // needs layout of the argument to be implemented fully - &[I32GtS] + self.code_builder.i32_gt_s() } _ => { return Err(format!("unsupported low-level op {:?}", lowlevel)); } }; - self.code_builder.extend_from_slice(instructions); Ok(()) } } diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs deleted file mode 100644 index f82e1132bc..0000000000 --- a/compiler/gen_wasm/src/code_builder.rs +++ /dev/null @@ -1,456 +0,0 @@ -use core::panic; -use std::collections::BTreeMap; -use std::fmt::Debug; - -use parity_wasm::elements::{Instruction, Instruction::*}; -use roc_module::symbol::Symbol; - -use crate::LocalId; - -const DEBUG_LOG: bool = false; - -#[derive(Debug, Clone, PartialEq, Copy)] -pub enum VirtualMachineSymbolState { - /// Value doesn't exist yet - NotYetPushed, - - /// Value has been pushed onto the VM stack but not yet popped - /// Remember where it was pushed, in case we need to insert another instruction there later - Pushed { pushed_at: usize }, - - /// Value has been pushed and popped, so it's not on the VM stack any more. - /// If we want to use it again later, we will have to create a local for it, - /// by going back to insert a local.tee instruction at pushed_at - Popped { pushed_at: usize }, -} - -#[derive(Debug)] -pub struct CodeBuilder { - /// The main container for the instructions - code: Vec, - - /// Extra instructions to insert at specific positions during finalisation - /// (Go back and set locals when we realise we need them) - /// We need BTree rather than Map or Vec, to ensure keys are sorted. - /// Entries may not be added in order. They are created when a Symbol - /// is used for the second time, or is in an inconvenient VM stack position, - /// so it's not a simple predictable order. - insertions: BTreeMap, - - /// Our simulation model of the Wasm stack machine - /// Keeps track of where Symbol values are in the VM stack - vm_stack: Vec, -} - -#[allow(clippy::new_without_default)] -impl CodeBuilder { - pub fn new() -> Self { - CodeBuilder { - vm_stack: Vec::with_capacity(32), - insertions: BTreeMap::default(), - code: Vec::with_capacity(1024), - } - } - - pub fn clear(&mut self) { - self.code.clear(); - self.insertions.clear(); - self.vm_stack.clear(); - } - - /// Add an instruction - pub fn push(&mut self, inst: Instruction) { - let (pops, push) = get_pops_and_pushes(&inst); - let new_len = self.vm_stack.len() - pops as usize; - self.vm_stack.truncate(new_len); - if push { - self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE); - } - if DEBUG_LOG { - println!("{:?} {:?}", inst, self.vm_stack); - } - self.code.push(inst); - } - - /// Add many instructions - pub fn extend_from_slice(&mut self, instructions: &[Instruction]) { - let old_len = self.vm_stack.len(); - let mut len = old_len; - let mut min_len = len; - for inst in instructions { - let (pops, push) = get_pops_and_pushes(inst); - len -= pops as usize; - if len < min_len { - min_len = len; - } - if push { - len += 1; - } - } - self.vm_stack.truncate(min_len); - self.vm_stack - .resize(len, Symbol::WASM_ANONYMOUS_STACK_VALUE); - if DEBUG_LOG { - println!("{:?} {:?}", instructions, self.vm_stack); - } - self.code.extend_from_slice(instructions); - } - - /// Special-case method to add a Call instruction - /// Specify the number of arguments the function pops from the VM stack, and whether it pushes a return value - pub fn push_call(&mut self, function_index: u32, pops: usize, push: bool) { - let stack_depth = self.vm_stack.len(); - if pops > stack_depth { - let mut final_code = Vec::with_capacity(self.code.len() + self.insertions.len()); - self.finalize_into(&mut final_code); - panic!( - "Trying to call to call function {:?} with {:?} values but only {:?} on the VM stack\nfinal_code={:?}\nvm_stack={:?}", - function_index, pops, stack_depth, final_code, self.vm_stack - ); - } - self.vm_stack.truncate(stack_depth - pops); - if push { - self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE); - } - let inst = Call(function_index); - if DEBUG_LOG { - println!("{:?} {:?}", inst, self.vm_stack); - } - self.code.push(inst); - } - - /// Finalize a function body by copying all instructions into a vector - pub fn finalize_into(&mut self, final_code: &mut Vec) { - let mut insertions_iter = self.insertions.iter(); - let mut next_insertion = insertions_iter.next(); - - for (pos, instruction) in self.code.drain(0..).enumerate() { - match next_insertion { - Some((&insert_pos, insert_inst)) if insert_pos == pos => { - final_code.push(insert_inst.to_owned()); - next_insertion = insertions_iter.next(); - } - _ => {} - } - final_code.push(instruction); - } - debug_assert!(next_insertion == None); - } - - /// Total number of instructions in the final output - pub fn len(&self) -> usize { - self.code.len() + self.insertions.len() - } - - /// Set the Symbol that is at the top of the VM stack right now - /// We will use this later when we need to load the Symbol - pub fn set_top_symbol(&mut self, sym: Symbol) -> VirtualMachineSymbolState { - let len = self.vm_stack.len(); - let pushed_at = self.code.len(); - - if len == 0 { - panic!( - "trying to set symbol with nothing on stack, code = {:?}", - self.code - ); - } - - self.vm_stack[len - 1] = sym; - - VirtualMachineSymbolState::Pushed { pushed_at } - } - - /// Verify if a sequence of symbols is at the top of the stack - pub fn verify_stack_match(&self, symbols: &[Symbol]) -> bool { - let n_symbols = symbols.len(); - let stack_depth = self.vm_stack.len(); - if n_symbols > stack_depth { - return false; - } - let offset = stack_depth - n_symbols; - - for (i, sym) in symbols.iter().enumerate() { - if self.vm_stack[offset + i] != *sym { - return false; - } - } - true - } - - /// Load a Symbol that is stored in the VM stack - /// If it's already at the top of the stack, no code will be generated. - /// Otherwise, local.set and local.get instructions will be inserted, using the LocalId provided. - /// - /// If the return value is `Some(s)`, `s` should be stored by the caller, and provided in the next call. - /// If the return value is `None`, the Symbol is no longer stored in the VM stack, but in a local. - /// (In this case, the caller must remember to declare the local in the function header.) - pub fn load_symbol( - &mut self, - symbol: Symbol, - vm_state: VirtualMachineSymbolState, - next_local_id: LocalId, - ) -> Option { - use VirtualMachineSymbolState::*; - - match vm_state { - NotYetPushed => panic!("Symbol {:?} has no value yet. Nothing to load.", symbol), - - Pushed { pushed_at } => { - let &top = self.vm_stack.last().unwrap(); - if top == symbol { - // We're lucky, the symbol is already on top of the VM stack - // No code to generate! (This reduces code size by up to 25% in tests.) - // Just let the caller know what happened - Some(Popped { pushed_at }) - } else { - // Symbol is not on top of the stack. Find it. - if let Some(found_index) = self.vm_stack.iter().rposition(|&s| s == symbol) { - // Insert a SetLocal where the value was created (this removes it from the VM stack) - self.insertions.insert(pushed_at, SetLocal(next_local_id.0)); - self.vm_stack.remove(found_index); - - // Insert a GetLocal at the current position - let inst = GetLocal(next_local_id.0); - if DEBUG_LOG { - println!( - "{:?} {:?} (& insert {:?} at {:?})", - inst, - self.vm_stack, - SetLocal(next_local_id.0), - pushed_at - ); - } - self.code.push(inst); - self.vm_stack.push(symbol); - - // This Symbol is no longer stored in the VM stack, but in a local - None - } else { - panic!( - "{:?} has state {:?} but not found in VM stack", - symbol, vm_state - ); - } - } - } - - Popped { pushed_at } => { - // This Symbol is being used for a second time - - // Insert a TeeLocal where it was created (must remain on the stack for the first usage) - self.insertions.insert(pushed_at, TeeLocal(next_local_id.0)); - - // Insert a GetLocal at the current position - let inst = GetLocal(next_local_id.0); - if DEBUG_LOG { - println!( - "{:?} {:?} (& insert {:?} at {:?})", - inst, - self.vm_stack, - TeeLocal(next_local_id.0), - pushed_at - ); - } - self.code.push(inst); - self.vm_stack.push(symbol); - - // This symbol has been promoted to a Local - // Tell the caller it no longer has a VirtualMachineSymbolState - None - } - } - } -} - -fn get_pops_and_pushes(inst: &Instruction) -> (u8, bool) { - match inst { - Unreachable => (0, false), - Nop => (0, false), - Block(_) => (0, false), - Loop(_) => (0, false), - If(_) => (1, false), - Else => (0, false), - End => (0, false), - Br(_) => (0, false), - BrIf(_) => (1, false), - BrTable(_) => (1, false), - Return => (0, false), - - Call(_) | CallIndirect(_, _) => { - panic!("Unknown number of pushes and pops. Use add_call()"); - } - - Drop => (1, false), - Select => (3, true), - - GetLocal(_) => (0, true), - SetLocal(_) => (1, false), - TeeLocal(_) => (1, true), - GetGlobal(_) => (0, true), - SetGlobal(_) => (1, false), - - I32Load(_, _) => (1, true), - I64Load(_, _) => (1, true), - F32Load(_, _) => (1, true), - F64Load(_, _) => (1, true), - I32Load8S(_, _) => (1, true), - I32Load8U(_, _) => (1, true), - I32Load16S(_, _) => (1, true), - I32Load16U(_, _) => (1, true), - I64Load8S(_, _) => (1, true), - I64Load8U(_, _) => (1, true), - I64Load16S(_, _) => (1, true), - I64Load16U(_, _) => (1, true), - I64Load32S(_, _) => (1, true), - I64Load32U(_, _) => (1, true), - I32Store(_, _) => (2, false), - I64Store(_, _) => (2, false), - F32Store(_, _) => (2, false), - F64Store(_, _) => (2, false), - I32Store8(_, _) => (2, false), - I32Store16(_, _) => (2, false), - I64Store8(_, _) => (2, false), - I64Store16(_, _) => (2, false), - I64Store32(_, _) => (2, false), - - CurrentMemory(_) => (0, true), - GrowMemory(_) => (1, true), - I32Const(_) => (0, true), - I64Const(_) => (0, true), - F32Const(_) => (0, true), - F64Const(_) => (0, true), - - I32Eqz => (1, true), - I32Eq => (2, true), - I32Ne => (2, true), - I32LtS => (2, true), - I32LtU => (2, true), - I32GtS => (2, true), - I32GtU => (2, true), - I32LeS => (2, true), - I32LeU => (2, true), - I32GeS => (2, true), - I32GeU => (2, true), - - I64Eqz => (1, true), - I64Eq => (2, true), - I64Ne => (2, true), - I64LtS => (2, true), - I64LtU => (2, true), - I64GtS => (2, true), - I64GtU => (2, true), - I64LeS => (2, true), - I64LeU => (2, true), - I64GeS => (2, true), - I64GeU => (2, true), - - F32Eq => (2, true), - F32Ne => (2, true), - F32Lt => (2, true), - F32Gt => (2, true), - F32Le => (2, true), - F32Ge => (2, true), - - F64Eq => (2, true), - F64Ne => (2, true), - F64Lt => (2, true), - F64Gt => (2, true), - F64Le => (2, true), - F64Ge => (2, true), - - I32Clz => (1, true), - I32Ctz => (1, true), - I32Popcnt => (1, true), - I32Add => (2, true), - I32Sub => (2, true), - I32Mul => (2, true), - I32DivS => (2, true), - I32DivU => (2, true), - I32RemS => (2, true), - I32RemU => (2, true), - I32And => (2, true), - I32Or => (2, true), - I32Xor => (2, true), - I32Shl => (2, true), - I32ShrS => (2, true), - I32ShrU => (2, true), - I32Rotl => (2, true), - I32Rotr => (2, true), - - I64Clz => (1, true), - I64Ctz => (1, true), - I64Popcnt => (1, true), - I64Add => (2, true), - I64Sub => (2, true), - I64Mul => (2, true), - I64DivS => (2, true), - I64DivU => (2, true), - I64RemS => (2, true), - I64RemU => (2, true), - I64And => (2, true), - I64Or => (2, true), - I64Xor => (2, true), - I64Shl => (2, true), - I64ShrS => (2, true), - I64ShrU => (2, true), - I64Rotl => (2, true), - I64Rotr => (2, true), - - F32Abs => (1, true), - F32Neg => (1, true), - F32Ceil => (1, true), - F32Floor => (1, true), - F32Trunc => (1, true), - F32Nearest => (1, true), - F32Sqrt => (1, true), - F32Add => (2, true), - F32Sub => (2, true), - F32Mul => (2, true), - F32Div => (2, true), - F32Min => (2, true), - F32Max => (2, true), - F32Copysign => (2, true), - - F64Abs => (1, true), - F64Neg => (1, true), - F64Ceil => (1, true), - F64Floor => (1, true), - F64Trunc => (1, true), - F64Nearest => (1, true), - F64Sqrt => (1, true), - F64Add => (2, true), - F64Sub => (2, true), - F64Mul => (2, true), - F64Div => (2, true), - F64Min => (2, true), - F64Max => (2, true), - F64Copysign => (2, true), - - I32WrapI64 => (1, true), - I32TruncSF32 => (1, true), - I32TruncUF32 => (1, true), - I32TruncSF64 => (1, true), - I32TruncUF64 => (1, true), - I64ExtendSI32 => (1, true), - I64ExtendUI32 => (1, true), - I64TruncSF32 => (1, true), - I64TruncUF32 => (1, true), - I64TruncSF64 => (1, true), - I64TruncUF64 => (1, true), - F32ConvertSI32 => (1, true), - F32ConvertUI32 => (1, true), - F32ConvertSI64 => (1, true), - F32ConvertUI64 => (1, true), - F32DemoteF64 => (1, true), - F64ConvertSI32 => (1, true), - F64ConvertUI32 => (1, true), - F64ConvertSI64 => (1, true), - F64ConvertUI64 => (1, true), - F64PromoteF32 => (1, true), - - I32ReinterpretF32 => (1, true), - I64ReinterpretF64 => (1, true), - F32ReinterpretI32 => (1, true), - F64ReinterpretI64 => (1, true), - } -} diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index 5227f7755a..49b2b6be93 100644 --- a/compiler/gen_wasm/src/layout.rs +++ b/compiler/gen_wasm/src/layout.rs @@ -1,7 +1,6 @@ -use parity_wasm::elements::ValueType; use roc_mono::layout::{Layout, UnionLayout}; -use crate::{PTR_SIZE, PTR_TYPE}; +use crate::{wasm_module::ValueType, PTR_SIZE, PTR_TYPE}; // See README for background information on Wasm locals, memory and function calls #[derive(Debug, Clone)] @@ -72,11 +71,7 @@ impl WasmLayout { } } - #[allow(dead_code)] - pub fn stack_memory(&self) -> u32 { - match self { - Self::StackMemory { size, .. } => *size, - _ => 0, - } + pub fn is_stack_memory(&self) -> bool { + matches!(self, Self::StackMemory { .. }) } } diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 6af623acf1..d5957574c9 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -1,12 +1,10 @@ mod backend; -mod code_builder; pub mod from_wasm32_memory; mod layout; mod storage; +pub mod wasm_module; -use bumpalo::Bump; -use parity_wasm::builder; -use parity_wasm::elements::{Instruction, Instruction::*, Internal, ValueType}; +use bumpalo::{self, collections::Vec, Bump}; use roc_collections::all::{MutMap, MutSet}; use roc_module::symbol::{Interns, Symbol}; @@ -14,25 +12,19 @@ use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::layout::LayoutIds; use crate::backend::WasmBackend; -use crate::code_builder::CodeBuilder; +use crate::wasm_module::{ + Align, CodeBuilder, Export, ExportType, Global, GlobalInitValue, GlobalType, LinkingSubSection, + LocalId, SymInfo, ValueType, WasmModule, +}; const PTR_SIZE: u32 = 4; const PTR_TYPE: ValueType = ValueType::I32; -// All usages of these alignment constants take u32, so an enum wouldn't add any safety. -pub const ALIGN_1: u32 = 0; -pub const ALIGN_2: u32 = 1; -pub const ALIGN_4: u32 = 2; -pub const ALIGN_8: u32 = 3; - pub const STACK_POINTER_GLOBAL_ID: u32 = 0; -pub const STACK_ALIGNMENT_BYTES: i32 = 16; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct LocalId(pub u32); +pub const FRAME_ALIGNMENT_BYTES: i32 = 16; pub struct Env<'a> { - pub arena: &'a Bump, // not really using this much, parity_wasm works with std::vec a lot + pub arena: &'a Bump, pub interns: Interns, pub exposed_to_host: MutSet, } @@ -40,87 +32,63 @@ pub struct Env<'a> { pub fn build_module<'a>( env: &'a Env, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, -) -> Result, String> { - let (builder, _) = build_module_help(env, procedures)?; - let module = builder.build(); - module - .to_bytes() - .map_err(|e| -> String { format!("Error serialising Wasm module {:?}", e) }) +) -> Result, String> { + let mut wasm_module = build_module_help(env, procedures)?; + let mut buffer = std::vec::Vec::with_capacity(4096); + wasm_module.serialize(&mut buffer); + Ok(buffer) } pub fn build_module_help<'a>( env: &'a Env, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, -) -> Result<(builder::ModuleBuilder, u32), String> { - let mut backend = WasmBackend::new(); +) -> Result, 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: std::vec::Vec<_> = procedures.into_iter().collect(); - 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)); + + 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) .to_symbol_string(sym, &env.interns); - let export = builder::export() - .field(fn_name.as_str()) - .with_internal(Internal::Function(function_index)) - .build(); - - backend.module_builder.push_export(export); + backend.module.export.entries.push(Export { + name: fn_name, + ty: ExportType::Func, + index: i as u32, + }); } } - // 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; + let symbol_table = LinkingSubSection::SymbolTable(symbol_table_entries); + backend.module.linking.subsections.push(symbol_table); - const MIN_MEMORY_SIZE_KB: u32 = 1024; - const PAGE_SIZE_KB: u32 = 64; + backend.module.export.entries.push(Export { + name: "memory".to_string(), + ty: ExportType::Mem, + index: 0, + }); - let memory = builder::MemoryBuilder::new() - .with_min(MIN_MEMORY_SIZE_KB / PAGE_SIZE_KB) - .build(); - backend.module_builder.push_memory(memory); - let memory_export = builder::export() - .field("memory") - .with_internal(Internal::Memory(0)) - .build(); - backend.module_builder.push_export(memory_export); + let stack_pointer_init = backend.module.memory.min_size().unwrap() as i32; + backend.module.global.entries.push(Global { + ty: GlobalType { + value_type: ValueType::I32, + is_mutable: true, + }, + init_value: GlobalInitValue::I32(stack_pointer_init), + }); - let stack_pointer_global = builder::global() - .with_type(PTR_TYPE) - .mutable() - .init_expr(Instruction::I32Const((MIN_MEMORY_SIZE_KB * 1024) as i32)) - .build(); - backend.module_builder.push_global(stack_pointer_global); - - Ok((backend.module_builder, main_function_index)) -} - -fn encode_alignment(bytes: u32) -> u32 { - match bytes { - 1 => ALIGN_1, - 2 => ALIGN_2, - 4 => ALIGN_4, - 8 => ALIGN_8, - _ => panic!("{:?}-byte alignment is not supported", bytes), - } + Ok(backend.module) } pub struct CopyMemoryConfig { @@ -137,33 +105,27 @@ pub fn copy_memory(code_builder: &mut CodeBuilder, config: CopyMemoryConfig) { return; } - let alignment_flag = encode_alignment(config.alignment_bytes); + let alignment = Align::from(config.alignment_bytes); let mut i = 0; while config.size - i >= 8 { - code_builder.extend_from_slice(&[ - GetLocal(config.to_ptr.0), - GetLocal(config.from_ptr.0), - I64Load(alignment_flag, i + config.from_offset), - I64Store(alignment_flag, i + config.to_offset), - ]); + code_builder.get_local(config.to_ptr); + code_builder.get_local(config.from_ptr); + code_builder.i64_load(alignment, i + config.from_offset); + code_builder.i64_store(alignment, i + config.to_offset); i += 8; } if config.size - i >= 4 { - code_builder.extend_from_slice(&[ - GetLocal(config.to_ptr.0), - GetLocal(config.from_ptr.0), - I32Load(alignment_flag, i + config.from_offset), - I32Store(alignment_flag, i + config.to_offset), - ]); + code_builder.get_local(config.to_ptr); + code_builder.get_local(config.from_ptr); + code_builder.i32_load(alignment, i + config.from_offset); + code_builder.i32_store(alignment, i + config.to_offset); i += 4; } while config.size - i > 0 { - code_builder.extend_from_slice(&[ - GetLocal(config.to_ptr.0), - GetLocal(config.from_ptr.0), - I32Load8U(alignment_flag, i + config.from_offset), - I32Store8(alignment_flag, i + config.to_offset), - ]); + code_builder.get_local(config.to_ptr); + code_builder.get_local(config.from_ptr); + code_builder.i32_load8_u(alignment, i + config.from_offset); + code_builder.i32_store8(alignment, i + config.to_offset); i += 1; } } @@ -177,31 +139,6 @@ pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 { aligned } -pub fn push_stack_frame( - instructions: &mut Vec, - size: i32, - local_frame_pointer: LocalId, -) { - let aligned_size = round_up_to_alignment(size, STACK_ALIGNMENT_BYTES); - instructions.extend([ - GetGlobal(STACK_POINTER_GLOBAL_ID), - I32Const(aligned_size), - I32Sub, - TeeLocal(local_frame_pointer.0), - SetGlobal(STACK_POINTER_GLOBAL_ID), - ]); -} - -pub fn pop_stack_frame( - instructions: &mut Vec, - size: i32, - local_frame_pointer: LocalId, -) { - let aligned_size = round_up_to_alignment(size, STACK_ALIGNMENT_BYTES); - instructions.extend([ - GetLocal(local_frame_pointer.0), - I32Const(aligned_size), - I32Add, - SetGlobal(STACK_POINTER_GLOBAL_ID), - ]); +pub fn debug_panic(error: E) { + panic!("{:?}", error); } diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index f0a5453c24..61ecd1347c 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -1,14 +1,12 @@ -use parity_wasm::elements::{Instruction::*, ValueType}; +use bumpalo::collections::Vec; +use bumpalo::Bump; use roc_collections::all::MutMap; use roc_module::symbol::Symbol; -use crate::code_builder::{CodeBuilder, VirtualMachineSymbolState}; use crate::layout::WasmLayout; -use crate::{ - copy_memory, round_up_to_alignment, CopyMemoryConfig, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, - ALIGN_8, PTR_SIZE, PTR_TYPE, -}; +use crate::wasm_module::{CodeBuilder, LocalId, ValueType, VirtualMachineSymbolState}; +use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_SIZE, PTR_TYPE}; pub enum StoredValueKind { Parameter, @@ -58,19 +56,19 @@ pub enum StoredValue { /// Helper structure for WasmBackend, to keep track of how values are stored, /// including the VM stack, local variables, and linear memory -pub struct Storage { - pub arg_types: std::vec::Vec, - pub local_types: std::vec::Vec, +pub struct Storage<'a> { + pub arg_types: Vec<'a, ValueType>, + pub local_types: Vec<'a, ValueType>, pub symbol_storage_map: MutMap, pub stack_frame_pointer: Option, pub stack_frame_size: i32, } -impl Storage { - pub fn new() -> Self { +impl<'a> Storage<'a> { + pub fn new(arena: &'a Bump) -> Self { Storage { - arg_types: std::vec::Vec::with_capacity(8), - local_types: std::vec::Vec::with_capacity(32), + arg_types: Vec::with_capacity_in(8, arena), + local_types: Vec::with_capacity_in(32, arena), symbol_storage_map: MutMap::default(), stack_frame_pointer: None, stack_frame_size: 0, @@ -239,7 +237,7 @@ impl Storage { location: StackMemoryLocation::PointerArg(local_id), .. } => { - code_builder.push(GetLocal(local_id.0)); + code_builder.get_local(local_id); code_builder.set_top_symbol(*sym); } @@ -247,11 +245,9 @@ impl Storage { location: StackMemoryLocation::FrameOffset(offset), .. } => { - code_builder.extend_from_slice(&[ - GetLocal(self.stack_frame_pointer.unwrap().0), - I32Const(offset as i32), - I32Add, - ]); + code_builder.get_local(self.stack_frame_pointer.unwrap()); + code_builder.i32_const(offset as i32); + code_builder.i32_add(); code_builder.set_top_symbol(*sym); } } @@ -295,20 +291,20 @@ impl Storage { | StoredValue::Local { value_type, size, .. } => { - let store_instruction = match (value_type, size) { - (ValueType::I64, 8) => I64Store(ALIGN_8, to_offset), - (ValueType::I32, 4) => I32Store(ALIGN_4, to_offset), - (ValueType::I32, 2) => I32Store16(ALIGN_2, to_offset), - (ValueType::I32, 1) => I32Store8(ALIGN_1, to_offset), - (ValueType::F32, 4) => F32Store(ALIGN_4, to_offset), - (ValueType::F64, 8) => F64Store(ALIGN_8, to_offset), + use crate::wasm_module::Align::*; + code_builder.get_local(to_ptr); + self.load_symbols(code_builder, &[from_symbol]); + match (value_type, size) { + (ValueType::I64, 8) => code_builder.i64_store(Bytes8, to_offset), + (ValueType::I32, 4) => code_builder.i32_store(Bytes4, to_offset), + (ValueType::I32, 2) => code_builder.i32_store16(Bytes2, to_offset), + (ValueType::I32, 1) => code_builder.i32_store8(Bytes1, to_offset), + (ValueType::F32, 4) => code_builder.f32_store(Bytes4, to_offset), + (ValueType::F64, 8) => code_builder.f64_store(Bytes8, to_offset), _ => { panic!("Cannot store {:?} with alignment of {:?}", value_type, size); } }; - code_builder.push(GetLocal(to_ptr.0)); - self.load_symbols(code_builder, &[from_symbol]); - code_builder.push(store_instruction); size } } @@ -341,7 +337,7 @@ impl Storage { debug_assert!(to_value_type == from_value_type); debug_assert!(to_size == from_size); self.load_symbols(code_builder, &[from_symbol]); - code_builder.push(SetLocal(to_local_id.0)); + code_builder.set_local(*to_local_id); self.symbol_storage_map.insert(from_symbol, to.clone()); } @@ -359,8 +355,8 @@ impl Storage { ) => { debug_assert!(to_value_type == from_value_type); debug_assert!(to_size == from_size); - code_builder - .extend_from_slice(&[GetLocal(from_local_id.0), SetLocal(to_local_id.0)]); + code_builder.get_local(*from_local_id); + code_builder.set_local(*to_local_id); } ( @@ -419,7 +415,7 @@ impl Storage { let local_id = self.get_next_local_id(); if vm_state != VirtualMachineSymbolState::NotYetPushed { code_builder.load_symbol(symbol, vm_state, local_id); - code_builder.push(SetLocal(local_id.0)); + code_builder.set_local(local_id); } self.local_types.push(value_type); diff --git a/compiler/gen_wasm/src/wasm_module/code_builder.rs b/compiler/gen_wasm/src/wasm_module/code_builder.rs new file mode 100644 index 0000000000..38561a986e --- /dev/null +++ b/compiler/gen_wasm/src/wasm_module/code_builder.rs @@ -0,0 +1,729 @@ +use bumpalo::collections::vec::Vec; +use bumpalo::Bump; +use core::panic; +use std::fmt::Debug; + +use roc_module::symbol::Symbol; + +use super::linking::{IndexRelocType, RelocationEntry}; +use super::opcodes::*; +use super::serialize::{SerialBuffer, Serialize}; +use crate::{round_up_to_alignment, FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct LocalId(pub u32); + +/// Wasm value type. (Rust representation matches Wasm encoding) +#[repr(u8)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum ValueType { + I32 = 0x7f, + I64 = 0x7e, + F32 = 0x7d, + F64 = 0x7c, +} + +impl Serialize for ValueType { + fn serialize(&self, buffer: &mut T) { + buffer.append_u8(*self as u8); + } +} + +pub enum BlockType { + NoResult, + Value(ValueType), +} + +impl BlockType { + pub fn as_byte(&self) -> u8 { + match self { + Self::NoResult => 0x40, + Self::Value(t) => *t as u8, + } + } +} + +/// Wasm memory alignment. (Rust representation matches Wasm encoding) +#[repr(u8)] +#[derive(Clone, Copy, Debug)] +pub enum Align { + Bytes1 = 0, + Bytes2 = 1, + Bytes4 = 2, + Bytes8 = 3, + Bytes16 = 4, + Bytes32 = 5, + Bytes64 = 6, + // ... we can add more if we need them ... +} + +impl From for Align { + fn from(x: u32) -> Align { + match x { + 1 => Align::Bytes1, + 2 => Align::Bytes2, + 4 => Align::Bytes4, + 8 => Align::Bytes8, + 16 => Align::Bytes16, + 32 => Align::Bytes32, + 64 => Align::Bytes64, + _ => panic!("{:?}-byte alignment not supported", x), + } + } +} + +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum VirtualMachineSymbolState { + /// Value doesn't exist yet + NotYetPushed, + + /// Value has been pushed onto the VM stack but not yet popped + /// Remember where it was pushed, in case we need to insert another instruction there later + Pushed { pushed_at: usize }, + + /// Value has been pushed and popped, so it's not on the VM stack any more. + /// If we want to use it again later, we will have to create a local for it, + /// by going back to insert a local.tee instruction at pushed_at + Popped { pushed_at: usize }, +} + +// An instruction (local.set or local.tee) to be inserted into the function code +#[derive(Debug)] +struct Insertion { + at: usize, + start: usize, + end: usize, +} + +macro_rules! instruction_no_args { + ($method_name: ident, $opcode: expr, $pops: expr, $push: expr) => { + pub fn $method_name(&mut self) { + self.inst($opcode, $pops, $push); + } + }; +} + +macro_rules! instruction_memargs { + ($method_name: ident, $opcode: expr, $pops: expr, $push: expr) => { + pub fn $method_name(&mut self, align: Align, offset: u32) { + self.inst_mem($opcode, $pops, $push, align, offset); + } + }; +} + +#[derive(Debug)] +pub struct CodeBuilder<'a> { + /// The main container for the instructions + code: Vec<'a, u8>, + + /// Instruction bytes to be inserted into the code when finalizing the function + /// (Used for setting locals when we realise they are used multiple times) + insert_bytes: Vec<'a, u8>, + + /// Code locations where the insert_bytes should go + insertions: Vec<'a, Insertion>, + + /// Bytes for local variable declarations and stack-frame setup code. + /// We can't write this until we've finished the main code. But it goes + /// before it in the final output, so we need a separate vector. + preamble: Vec<'a, u8>, + + /// Encoded bytes for the inner length of the function, locals + code. + /// ("inner" because it doesn't include its own length!) + /// Again, we can't write this until we've finished the code and preamble, + /// but it goes before them in the binary, so it's a separate vector. + inner_length: Vec<'a, u8>, + + /// 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)] +impl<'a> CodeBuilder<'a> { + pub fn new(arena: &'a Bump) -> Self { + CodeBuilder { + code: Vec::with_capacity_in(1024, arena), + insertions: Vec::with_capacity_in(32, arena), + insert_bytes: Vec::with_capacity_in(64, arena), + 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), + } + } + + /********************************************************** + + SYMBOLS + + The Wasm VM stores temporary values in its stack machine. + We track which stack positions correspond to IR Symbols, + because it helps to generate more efficient code. + + ***********************************************************/ + + /// Set the Symbol that is at the top of the VM stack right now + /// We will use this later when we need to load the Symbol + pub fn set_top_symbol(&mut self, sym: Symbol) -> VirtualMachineSymbolState { + let len = self.vm_stack.len(); + let pushed_at = self.code.len(); + + if len == 0 { + panic!( + "trying to set symbol with nothing on stack, code = {:?}", + self.code + ); + } + + self.vm_stack[len - 1] = sym; + + VirtualMachineSymbolState::Pushed { pushed_at } + } + + /// Verify if a sequence of symbols is at the top of the stack + pub fn verify_stack_match(&self, symbols: &[Symbol]) -> bool { + let n_symbols = symbols.len(); + let stack_depth = self.vm_stack.len(); + if n_symbols > stack_depth { + return false; + } + let offset = stack_depth - n_symbols; + + for (i, sym) in symbols.iter().enumerate() { + if self.vm_stack[offset + i] != *sym { + return false; + } + } + true + } + + fn add_insertion(&mut self, insert_at: usize, opcode: u8, immediate: u32) { + let start = self.insert_bytes.len(); + + self.insert_bytes.push(opcode); + self.insert_bytes.encode_u32(immediate); + + self.insertions.push(Insertion { + at: insert_at, + start, + end: self.insert_bytes.len(), + }); + } + + /// Load a Symbol that is stored in the VM stack + /// If it's already at the top of the stack, no code will be generated. + /// Otherwise, local.set and local.get instructions will be inserted, using the LocalId provided. + /// + /// If the return value is `Some(s)`, `s` should be stored by the caller, and provided in the next call. + /// If the return value is `None`, the Symbol is no longer stored in the VM stack, but in a local. + /// (In this case, the caller must remember to declare the local in the function header.) + pub fn load_symbol( + &mut self, + symbol: Symbol, + vm_state: VirtualMachineSymbolState, + next_local_id: LocalId, + ) -> Option { + use VirtualMachineSymbolState::*; + + match vm_state { + NotYetPushed => panic!("Symbol {:?} has no value yet. Nothing to load.", symbol), + + Pushed { pushed_at } => { + let &top = self.vm_stack.last().unwrap(); + if top == symbol { + // We're lucky, the symbol is already on top of the VM stack + // No code to generate! (This reduces code size by up to 25% in tests.) + // Just let the caller know what happened + Some(Popped { pushed_at }) + } else { + // Symbol is not on top of the stack. Find it. + if let Some(found_index) = self.vm_stack.iter().rposition(|&s| s == symbol) { + // Insert a local.set where the value was created + self.add_insertion(pushed_at, SETLOCAL, next_local_id.0); + + // Take the value out of the stack where local.set was inserted + self.vm_stack.remove(found_index); + + // Insert a local.get at the current position + self.get_local(next_local_id); + self.vm_stack.push(symbol); + + // This Symbol is no longer stored in the VM stack, but in a local + None + } else { + panic!( + "{:?} has state {:?} but not found in VM stack", + symbol, vm_state + ); + } + } + } + + Popped { pushed_at } => { + // This Symbol is being used for a second time + // Insert a local.tee where it was pushed, so we don't interfere with the first usage + self.add_insertion(pushed_at, TEELOCAL, next_local_id.0); + + // Insert a local.get at the current position + self.get_local(next_local_id); + self.vm_stack.push(symbol); + + // This symbol has been promoted to a Local + // Tell the caller it no longer has a VirtualMachineSymbolState + None + } + } + } + + /********************************************************** + + FINALIZE AND SERIALIZE + + ***********************************************************/ + + /// Generate bytes to declare the function's local variables + fn build_local_declarations(&mut self, local_types: &[ValueType]) { + // reserve one byte for num_batches + self.preamble.push(0); + + if local_types.is_empty() { + return; + } + + // Write declarations in batches of the same ValueType + let mut num_batches: u32 = 0; + let mut batch_type = local_types[0]; + let mut batch_size = 0; + for t in local_types { + if *t == batch_type { + batch_size += 1; + } else { + self.preamble.encode_u32(batch_size); + self.preamble.push(batch_type as u8); + batch_type = *t; + batch_size = 1; + num_batches += 1; + } + } + self.preamble.encode_u32(batch_size); + self.preamble.push(batch_type as u8); + num_batches += 1; + + // Go back and write the number of batches at the start + if num_batches < 128 { + self.preamble[0] = num_batches as u8; + } else { + // We need more than 1 byte to encode num_batches! + // This is a ridiculous edge case, so just pad to 5 bytes for simplicity + let old_len = self.preamble.len(); + self.preamble.resize(old_len + 4, 0); + self.preamble.copy_within(1..old_len, 5); + self.preamble.overwrite_padded_u32(0, num_batches); + } + } + + /// Generate instruction bytes to grab a frame of stack memory on entering the function + fn build_stack_frame_push(&mut self, frame_size: i32, frame_pointer: LocalId) { + // 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); + self.preamble.encode_u32(STACK_POINTER_GLOBAL_ID); + self.preamble.push(I32CONST); + self.preamble.encode_i32(frame_size); + self.preamble.push(I32SUB); + self.preamble.push(TEELOCAL); + self.preamble.encode_u32(frame_pointer.0); + self.preamble.push(SETGLOBAL); + self.preamble.encode_u32(STACK_POINTER_GLOBAL_ID); + } + + /// Generate instruction bytes to release a frame of stack memory on leaving the function + fn build_stack_frame_pop(&mut self, frame_size: i32, frame_pointer: LocalId) { + self.get_local(frame_pointer); + self.i32_const(frame_size); + self.i32_add(); + self.set_global(STACK_POINTER_GLOBAL_ID); + } + + /// Finalize the function + /// Generate all the "extra" bytes: local declarations, stack frame push/pop code, and function length + /// After this, bytes will have been _generated_, but not yet _serialized_ into a single stream. + /// Returns the final number of bytes the function will occupy in the target binary + pub fn finalize( + &mut self, + local_types: &[ValueType], + frame_size: i32, + frame_pointer: Option, + ) { + self.build_local_declarations(local_types); + + if let Some(frame_ptr_id) = frame_pointer { + let aligned_size = round_up_to_alignment(frame_size, FRAME_ALIGNMENT_BYTES); + self.build_stack_frame_push(aligned_size, frame_ptr_id); + self.build_stack_frame_pop(aligned_size, frame_ptr_id); + } + + self.code.push(END); + + let inner_len = self.preamble.len() + self.code.len() + self.insert_bytes.len(); + self.inner_length.encode_u32(inner_len as u32); + + // Sort insertions. They are not created in order of assignment, but in order of *second* usage. + self.insertions.sort_by_key(|ins| ins.at); + } + + /// Serialize all byte vectors in the right order + /// Also update relocation offsets relative to the provided base offset in the buffer + pub fn serialize_with_relocs( + &self, + buffer: &mut T, + final_relocs: &mut Vec<'a, RelocationEntry>, + reloc_base_offset: usize, + ) { + buffer.append_slice(&self.inner_length); + buffer.append_slice(&self.preamble); + + // Do the insertions & update relocation offsets + let mut reloc_index = 0; + let mut code_pos = 0; + let mut insert_iter = self.insertions.iter(); + + loop { + let next_insert = insert_iter.next(); + let next_pos = next_insert.map(|i| i.at).unwrap_or_else(|| self.code.len()); + + // 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 = buffer.size() - reloc_base_offset; + while reloc_index < self.relocations.len() + && self.relocations[reloc_index].offset() < next_pos as u32 + { + let mut reloc_clone = self.relocations[reloc_index].clone(); + *reloc_clone.offset_mut() += (section_body_pos - code_pos) as u32; + final_relocs.push(reloc_clone); + reloc_index += 1; + } + + buffer.append_slice(&self.code[code_pos..next_pos]); + + match next_insert { + Some(Insertion { at, start, end }) => { + buffer.append_slice(&self.insert_bytes[*start..*end]); + code_pos = *at; + } + None => { + break; + } + } + } + } + + /********************************************************** + + INSTRUCTION HELPER METHODS + + ***********************************************************/ + + /// Base method for generating instructions + /// Emits the opcode and simulates VM stack push/pop + fn inst(&mut self, opcode: u8, pops: usize, push: bool) { + let new_len = self.vm_stack.len() - pops as usize; + self.vm_stack.truncate(new_len); + if push { + self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE); + } + self.code.push(opcode); + } + + fn inst_imm8(&mut self, opcode: u8, pops: usize, push: bool, immediate: u8) { + self.inst(opcode, pops, push); + self.code.push(immediate); + } + + // 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); + 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); + self.code.encode_u32(offset); + } + + /********************************************************** + + INSTRUCTION METHODS + + One method for each Wasm instruction (in same order as the spec) + macros are for compactness & readability for the most common cases + Patterns that don't repeat very much don't have macros + + ***********************************************************/ + + instruction_no_args!(unreachable_, UNREACHABLE, 0, false); + instruction_no_args!(nop, NOP, 0, false); + + pub fn block(&mut self, ty: BlockType) { + self.inst_imm8(BLOCK, 0, false, ty.as_byte()); + } + pub fn loop_(&mut self, ty: BlockType) { + self.inst_imm8(LOOP, 0, false, ty.as_byte()); + } + pub fn if_(&mut self, ty: BlockType) { + self.inst_imm8(IF, 1, false, ty.as_byte()); + } + + instruction_no_args!(else_, ELSE, 0, false); + instruction_no_args!(end, END, 0, false); + + pub fn br(&mut self, levels: u32) { + self.inst_imm32(BR, 0, false, levels); + } + 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, + symbol_index: u32, + n_args: usize, + has_return_val: bool, + ) { + let stack_depth = self.vm_stack.len(); + if n_args > stack_depth { + panic!( + "Trying to call to call function {:?} with {:?} values but only {:?} on the VM stack\n{:?}", + function_index, n_args, stack_depth, self + ); + } + self.vm_stack.truncate(stack_depth - n_args); + if has_return_val { + self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE); + } + self.code.push(CALL); + + // 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"); + } + + instruction_no_args!(drop_, DROP, 1, false); + instruction_no_args!(select, SELECT, 3, true); + + pub fn get_local(&mut self, id: LocalId) { + self.inst_imm32(GETLOCAL, 0, true, id.0); + } + pub fn set_local(&mut self, id: LocalId) { + self.inst_imm32(SETLOCAL, 1, false, id.0); + } + pub fn tee_local(&mut self, id: LocalId) { + self.inst_imm32(TEELOCAL, 0, false, id.0); + } + pub fn get_global(&mut self, id: u32) { + self.inst_imm32(GETGLOBAL, 0, true, id); + } + pub fn set_global(&mut self, id: u32) { + self.inst_imm32(SETGLOBAL, 1, false, id); + } + + instruction_memargs!(i32_load, I32LOAD, 1, true); + instruction_memargs!(i64_load, I64LOAD, 1, true); + instruction_memargs!(f32_load, F32LOAD, 1, true); + instruction_memargs!(f64_load, F64LOAD, 1, true); + instruction_memargs!(i32_load8_s, I32LOAD8S, 1, true); + instruction_memargs!(i32_load8_u, I32LOAD8U, 1, true); + instruction_memargs!(i32_load16_s, I32LOAD16S, 1, true); + instruction_memargs!(i32_load16_u, I32LOAD16U, 1, true); + instruction_memargs!(i64_load8_s, I64LOAD8S, 1, true); + instruction_memargs!(i64_load8_u, I64LOAD8U, 1, true); + instruction_memargs!(i64_load16_s, I64LOAD16S, 1, true); + instruction_memargs!(i64_load16_u, I64LOAD16U, 1, true); + instruction_memargs!(i64_load32_s, I64LOAD32S, 1, true); + instruction_memargs!(i64_load32_u, I64LOAD32U, 1, true); + instruction_memargs!(i32_store, I32STORE, 2, false); + instruction_memargs!(i64_store, I64STORE, 2, false); + instruction_memargs!(f32_store, F32STORE, 2, false); + instruction_memargs!(f64_store, F64STORE, 2, false); + instruction_memargs!(i32_store8, I32STORE8, 2, false); + instruction_memargs!(i32_store16, I32STORE16, 2, false); + instruction_memargs!(i64_store8, I64STORE8, 2, false); + instruction_memargs!(i64_store16, I64STORE16, 2, false); + instruction_memargs!(i64_store32, I64STORE32, 2, false); + + pub fn memory_size(&mut self) { + self.inst_imm8(CURRENTMEMORY, 0, true, 0); + } + pub fn memory_grow(&mut self) { + self.inst_imm8(GROWMEMORY, 1, true, 0); + } + pub fn i32_const(&mut self, x: i32) { + self.inst(I32CONST, 0, true); + self.code.encode_i32(x); + } + pub fn i64_const(&mut self, x: i64) { + self.inst(I64CONST, 0, true); + self.code.encode_i64(x); + } + pub fn f32_const(&mut self, x: f32) { + self.inst(F32CONST, 0, true); + self.code.encode_f32(x); + } + pub fn f64_const(&mut self, x: f64) { + self.inst(F64CONST, 0, true); + self.code.encode_f64(x); + } + + // TODO: Consider creating unified methods for numerical ops like 'eq' and 'add', + // passing the ValueType as an argument. Could simplify lowlevel code gen. + instruction_no_args!(i32_eqz, I32EQZ, 1, true); + instruction_no_args!(i32_eq, I32EQ, 2, true); + instruction_no_args!(i32_ne, I32NE, 2, true); + instruction_no_args!(i32_lt_s, I32LTS, 2, true); + instruction_no_args!(i32_lt_u, I32LTU, 2, true); + instruction_no_args!(i32_gt_s, I32GTS, 2, true); + instruction_no_args!(i32_gt_u, I32GTU, 2, true); + instruction_no_args!(i32_le_s, I32LES, 2, true); + instruction_no_args!(i32_le_u, I32LEU, 2, true); + instruction_no_args!(i32_ge_s, I32GES, 2, true); + instruction_no_args!(i32_ge_u, I32GEU, 2, true); + instruction_no_args!(i64_eqz, I64EQZ, 1, true); + instruction_no_args!(i64_eq, I64EQ, 2, true); + instruction_no_args!(i64_ne, I64NE, 2, true); + instruction_no_args!(i64_lt_s, I64LTS, 2, true); + instruction_no_args!(i64_lt_u, I64LTU, 2, true); + instruction_no_args!(i64_gt_s, I64GTS, 2, true); + instruction_no_args!(i64_gt_u, I64GTU, 2, true); + instruction_no_args!(i64_le_s, I64LES, 2, true); + instruction_no_args!(i64_le_u, I64LEU, 2, true); + instruction_no_args!(i64_ge_s, I64GES, 2, true); + instruction_no_args!(i64_ge_u, I64GEU, 2, true); + instruction_no_args!(f32_eq, F32EQ, 2, true); + instruction_no_args!(f32_ne, F32NE, 2, true); + instruction_no_args!(f32_lt, F32LT, 2, true); + instruction_no_args!(f32_gt, F32GT, 2, true); + instruction_no_args!(f32_le, F32LE, 2, true); + instruction_no_args!(f32_ge, F32GE, 2, true); + instruction_no_args!(f64_eq, F64EQ, 2, true); + instruction_no_args!(f64_ne, F64NE, 2, true); + instruction_no_args!(f64_lt, F64LT, 2, true); + instruction_no_args!(f64_gt, F64GT, 2, true); + instruction_no_args!(f64_le, F64LE, 2, true); + instruction_no_args!(f64_ge, F64GE, 2, true); + instruction_no_args!(i32_clz, I32CLZ, 1, true); + instruction_no_args!(i32_ctz, I32CTZ, 1, true); + instruction_no_args!(i32_popcnt, I32POPCNT, 1, true); + instruction_no_args!(i32_add, I32ADD, 2, true); + instruction_no_args!(i32_sub, I32SUB, 2, true); + instruction_no_args!(i32_mul, I32MUL, 2, true); + instruction_no_args!(i32_div_s, I32DIVS, 2, true); + instruction_no_args!(i32_div_u, I32DIVU, 2, true); + instruction_no_args!(i32_rem_s, I32REMS, 2, true); + instruction_no_args!(i32_rem_u, I32REMU, 2, true); + instruction_no_args!(i32_and, I32AND, 2, true); + instruction_no_args!(i32_or, I32OR, 2, true); + instruction_no_args!(i32_xor, I32XOR, 2, true); + instruction_no_args!(i32_shl, I32SHL, 2, true); + instruction_no_args!(i32_shr_s, I32SHRS, 2, true); + instruction_no_args!(i32_shr_u, I32SHRU, 2, true); + instruction_no_args!(i32_rotl, I32ROTL, 2, true); + instruction_no_args!(i32_rotr, I32ROTR, 2, true); + instruction_no_args!(i64_clz, I64CLZ, 1, true); + instruction_no_args!(i64_ctz, I64CTZ, 1, true); + instruction_no_args!(i64_popcnt, I64POPCNT, 1, true); + instruction_no_args!(i64_add, I64ADD, 2, true); + instruction_no_args!(i64_sub, I64SUB, 2, true); + instruction_no_args!(i64_mul, I64MUL, 2, true); + instruction_no_args!(i64_div_s, I64DIVS, 2, true); + instruction_no_args!(i64_div_u, I64DIVU, 2, true); + instruction_no_args!(i64_rem_s, I64REMS, 2, true); + instruction_no_args!(i64_rem_u, I64REMU, 2, true); + instruction_no_args!(i64_and, I64AND, 2, true); + instruction_no_args!(i64_or, I64OR, 2, true); + instruction_no_args!(i64_xor, I64XOR, 2, true); + instruction_no_args!(i64_shl, I64SHL, 2, true); + instruction_no_args!(i64_shr_s, I64SHRS, 2, true); + instruction_no_args!(i64_shr_u, I64SHRU, 2, true); + instruction_no_args!(i64_rotl, I64ROTL, 2, true); + instruction_no_args!(i64_rotr, I64ROTR, 2, true); + instruction_no_args!(f32_abs, F32ABS, 1, true); + instruction_no_args!(f32_neg, F32NEG, 1, true); + instruction_no_args!(f32_ceil, F32CEIL, 1, true); + instruction_no_args!(f32_floor, F32FLOOR, 1, true); + instruction_no_args!(f32_trunc, F32TRUNC, 1, true); + instruction_no_args!(f32_nearest, F32NEAREST, 1, true); + instruction_no_args!(f32_sqrt, F32SQRT, 1, true); + instruction_no_args!(f32_add, F32ADD, 2, true); + instruction_no_args!(f32_sub, F32SUB, 2, true); + instruction_no_args!(f32_mul, F32MUL, 2, true); + instruction_no_args!(f32_div, F32DIV, 2, true); + instruction_no_args!(f32_min, F32MIN, 2, true); + instruction_no_args!(f32_max, F32MAX, 2, true); + instruction_no_args!(f32_copysign, F32COPYSIGN, 2, true); + instruction_no_args!(f64_abs, F64ABS, 1, true); + instruction_no_args!(f64_neg, F64NEG, 1, true); + instruction_no_args!(f64_ceil, F64CEIL, 1, true); + instruction_no_args!(f64_floor, F64FLOOR, 1, true); + instruction_no_args!(f64_trunc, F64TRUNC, 1, true); + instruction_no_args!(f64_nearest, F64NEAREST, 1, true); + instruction_no_args!(f64_sqrt, F64SQRT, 1, true); + instruction_no_args!(f64_add, F64ADD, 2, true); + instruction_no_args!(f64_sub, F64SUB, 2, true); + instruction_no_args!(f64_mul, F64MUL, 2, true); + instruction_no_args!(f64_div, F64DIV, 2, true); + instruction_no_args!(f64_min, F64MIN, 2, true); + instruction_no_args!(f64_max, F64MAX, 2, true); + instruction_no_args!(f64_copysign, F64COPYSIGN, 2, true); + instruction_no_args!(i32_wrap_i64, I32WRAPI64, 1, true); + instruction_no_args!(i32_trunc_s_f32, I32TRUNCSF32, 1, true); + instruction_no_args!(i32_trunc_u_f32, I32TRUNCUF32, 1, true); + instruction_no_args!(i32_trunc_s_f64, I32TRUNCSF64, 1, true); + instruction_no_args!(i32_trunc_u_f64, I32TRUNCUF64, 1, true); + instruction_no_args!(i64_extend_s_i32, I64EXTENDSI32, 1, true); + instruction_no_args!(i64_extend_u_i32, I64EXTENDUI32, 1, true); + instruction_no_args!(i64_trunc_s_f32, I64TRUNCSF32, 1, true); + instruction_no_args!(i64_trunc_u_f32, I64TRUNCUF32, 1, true); + instruction_no_args!(i64_trunc_s_f64, I64TRUNCSF64, 1, true); + instruction_no_args!(i64_trunc_u_f64, I64TRUNCUF64, 1, true); + instruction_no_args!(f32_convert_s_i32, F32CONVERTSI32, 1, true); + instruction_no_args!(f32_convert_u_i32, F32CONVERTUI32, 1, true); + instruction_no_args!(f32_convert_s_i64, F32CONVERTSI64, 1, true); + instruction_no_args!(f32_convert_u_i64, F32CONVERTUI64, 1, true); + instruction_no_args!(f32_demote_f64, F32DEMOTEF64, 1, true); + instruction_no_args!(f64_convert_s_i32, F64CONVERTSI32, 1, true); + instruction_no_args!(f64_convert_u_i32, F64CONVERTUI32, 1, true); + instruction_no_args!(f64_convert_s_i64, F64CONVERTSI64, 1, true); + instruction_no_args!(f64_convert_u_i64, F64CONVERTUI64, 1, true); + instruction_no_args!(f64_promote_f32, F64PROMOTEF32, 1, true); + instruction_no_args!(i32_reinterpret_f32, I32REINTERPRETF32, 1, true); + instruction_no_args!(i64_reinterpret_f64, I64REINTERPRETF64, 1, true); + instruction_no_args!(f32_reinterpret_i32, F32REINTERPRETI32, 1, true); + instruction_no_args!(f64_reinterpret_i64, F64REINTERPRETI64, 1, true); +} diff --git a/compiler/gen_wasm/src/wasm_module/linking.rs b/compiler/gen_wasm/src/wasm_module/linking.rs new file mode 100644 index 0000000000..bf2875b770 --- /dev/null +++ b/compiler/gen_wasm/src/wasm_module/linking.rs @@ -0,0 +1,462 @@ +use bumpalo::collections::vec::Vec; +use bumpalo::Bump; + +use super::sections::{update_section_size, write_custom_section_header}; +use super::serialize::{SerialBuffer, Serialize}; +use super::Align; + +/******************************************************************* + * + * 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, Clone)] +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_u8(*type_id as u8); + buffer.encode_u32(*offset); + buffer.encode_u32(*symbol_index); + } + Self::Offset { + type_id, + offset, + symbol_index, + addend, + } => { + buffer.append_u8(*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: Option, + pub entries: Vec<'a, RelocationEntry>, +} + +impl<'a> RelocationSection<'a> { + pub fn new(arena: &'a Bump, name: &'a str) -> Self { + RelocationSection { + name, + target_section_index: None, + entries: Vec::with_capacity_in(64, arena), + } + } +} + +impl<'a> Serialize for RelocationSection<'a> { + fn serialize(&self, buffer: &mut T) { + if !self.entries.is_empty() { + let header_indices = write_custom_section_header(buffer, self.name); + buffer.encode_u32(self.target_section_index.unwrap()); + self.entries.serialize(buffer); + 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_u8(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_u8(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) => items.serialize(buffer), + Self::InitFuncs(items) => items.serialize(buffer), + Self::ComdatInfo(items) => items.serialize(buffer), + Self::SymbolTable(items) => items.serialize(buffer), + } + buffer.overwrite_padded_u32( + payload_len_index, + (buffer.size() - payload_start_index) as u32, + ); + } +} + +//---------------------------------------------------------------- +// Linking metadata section +//---------------------------------------------------------------- + +const LINKING_VERSION: u8 = 2; + +pub struct LinkingSection<'a> { + pub subsections: Vec<'a, LinkingSubSection<'a>>, +} + +impl<'a> LinkingSection<'a> { + pub fn new(arena: &'a Bump) -> Self { + LinkingSection { + subsections: Vec::with_capacity_in(1, arena), + } + } +} + +impl<'a> Serialize for LinkingSection<'a> { + fn serialize(&self, buffer: &mut T) { + let header_indices = write_custom_section_header(buffer, "linking"); + buffer.append_u8(LINKING_VERSION); + for subsection in self.subsections.iter() { + subsection.serialize(buffer); + } + update_section_size(buffer, header_indices); + } +} diff --git a/compiler/gen_wasm/src/wasm_module/mod.rs b/compiler/gen_wasm/src/wasm_module/mod.rs new file mode 100644 index 0000000000..d4d649e6c0 --- /dev/null +++ b/compiler/gen_wasm/src/wasm_module/mod.rs @@ -0,0 +1,13 @@ +pub mod code_builder; +pub mod linking; +pub mod opcodes; +pub mod sections; +pub mod serialize; + +pub use code_builder::{ + Align, BlockType, CodeBuilder, LocalId, ValueType, VirtualMachineSymbolState, +}; +pub use linking::{LinkingSubSection, SymInfo}; +pub use sections::{ + Export, ExportType, Global, GlobalInitValue, GlobalType, Signature, WasmModule, +}; diff --git a/compiler/gen_wasm/src/wasm_module/opcodes.rs b/compiler/gen_wasm/src/wasm_module/opcodes.rs new file mode 100644 index 0000000000..714889bdae --- /dev/null +++ b/compiler/gen_wasm/src/wasm_module/opcodes.rs @@ -0,0 +1,178 @@ +pub const UNREACHABLE: u8 = 0x00; +pub const NOP: u8 = 0x01; +pub const BLOCK: u8 = 0x02; +pub const LOOP: u8 = 0x03; +pub const IF: u8 = 0x04; +pub const ELSE: u8 = 0x05; +pub const END: u8 = 0x0b; +pub const BR: u8 = 0x0c; +pub const BRIF: u8 = 0x0d; +pub const BRTABLE: u8 = 0x0e; +pub const RETURN: u8 = 0x0f; +pub const CALL: u8 = 0x10; +pub const CALLINDIRECT: u8 = 0x11; +pub const DROP: u8 = 0x1a; +pub const SELECT: u8 = 0x1b; +pub const GETLOCAL: u8 = 0x20; +pub const SETLOCAL: u8 = 0x21; +pub const TEELOCAL: u8 = 0x22; +pub const GETGLOBAL: u8 = 0x23; +pub const SETGLOBAL: u8 = 0x24; +pub const I32LOAD: u8 = 0x28; +pub const I64LOAD: u8 = 0x29; +pub const F32LOAD: u8 = 0x2a; +pub const F64LOAD: u8 = 0x2b; +pub const I32LOAD8S: u8 = 0x2c; +pub const I32LOAD8U: u8 = 0x2d; +pub const I32LOAD16S: u8 = 0x2e; +pub const I32LOAD16U: u8 = 0x2f; +pub const I64LOAD8S: u8 = 0x30; +pub const I64LOAD8U: u8 = 0x31; +pub const I64LOAD16S: u8 = 0x32; +pub const I64LOAD16U: u8 = 0x33; +pub const I64LOAD32S: u8 = 0x34; +pub const I64LOAD32U: u8 = 0x35; +pub const I32STORE: u8 = 0x36; +pub const I64STORE: u8 = 0x37; +pub const F32STORE: u8 = 0x38; +pub const F64STORE: u8 = 0x39; +pub const I32STORE8: u8 = 0x3a; +pub const I32STORE16: u8 = 0x3b; +pub const I64STORE8: u8 = 0x3c; +pub const I64STORE16: u8 = 0x3d; +pub const I64STORE32: u8 = 0x3e; +pub const CURRENTMEMORY: u8 = 0x3f; +pub const GROWMEMORY: u8 = 0x40; +pub const I32CONST: u8 = 0x41; +pub const I64CONST: u8 = 0x42; +pub const F32CONST: u8 = 0x43; +pub const F64CONST: u8 = 0x44; +pub const I32EQZ: u8 = 0x45; +pub const I32EQ: u8 = 0x46; +pub const I32NE: u8 = 0x47; +pub const I32LTS: u8 = 0x48; +pub const I32LTU: u8 = 0x49; +pub const I32GTS: u8 = 0x4a; +pub const I32GTU: u8 = 0x4b; +pub const I32LES: u8 = 0x4c; +pub const I32LEU: u8 = 0x4d; +pub const I32GES: u8 = 0x4e; +pub const I32GEU: u8 = 0x4f; +pub const I64EQZ: u8 = 0x50; +pub const I64EQ: u8 = 0x51; +pub const I64NE: u8 = 0x52; +pub const I64LTS: u8 = 0x53; +pub const I64LTU: u8 = 0x54; +pub const I64GTS: u8 = 0x55; +pub const I64GTU: u8 = 0x56; +pub const I64LES: u8 = 0x57; +pub const I64LEU: u8 = 0x58; +pub const I64GES: u8 = 0x59; +pub const I64GEU: u8 = 0x5a; + +pub const F32EQ: u8 = 0x5b; +pub const F32NE: u8 = 0x5c; +pub const F32LT: u8 = 0x5d; +pub const F32GT: u8 = 0x5e; +pub const F32LE: u8 = 0x5f; +pub const F32GE: u8 = 0x60; + +pub const F64EQ: u8 = 0x61; +pub const F64NE: u8 = 0x62; +pub const F64LT: u8 = 0x63; +pub const F64GT: u8 = 0x64; +pub const F64LE: u8 = 0x65; +pub const F64GE: u8 = 0x66; + +pub const I32CLZ: u8 = 0x67; +pub const I32CTZ: u8 = 0x68; +pub const I32POPCNT: u8 = 0x69; +pub const I32ADD: u8 = 0x6a; +pub const I32SUB: u8 = 0x6b; +pub const I32MUL: u8 = 0x6c; +pub const I32DIVS: u8 = 0x6d; +pub const I32DIVU: u8 = 0x6e; +pub const I32REMS: u8 = 0x6f; +pub const I32REMU: u8 = 0x70; +pub const I32AND: u8 = 0x71; +pub const I32OR: u8 = 0x72; +pub const I32XOR: u8 = 0x73; +pub const I32SHL: u8 = 0x74; +pub const I32SHRS: u8 = 0x75; +pub const I32SHRU: u8 = 0x76; +pub const I32ROTL: u8 = 0x77; +pub const I32ROTR: u8 = 0x78; + +pub const I64CLZ: u8 = 0x79; +pub const I64CTZ: u8 = 0x7a; +pub const I64POPCNT: u8 = 0x7b; +pub const I64ADD: u8 = 0x7c; +pub const I64SUB: u8 = 0x7d; +pub const I64MUL: u8 = 0x7e; +pub const I64DIVS: u8 = 0x7f; +pub const I64DIVU: u8 = 0x80; +pub const I64REMS: u8 = 0x81; +pub const I64REMU: u8 = 0x82; +pub const I64AND: u8 = 0x83; +pub const I64OR: u8 = 0x84; +pub const I64XOR: u8 = 0x85; +pub const I64SHL: u8 = 0x86; +pub const I64SHRS: u8 = 0x87; +pub const I64SHRU: u8 = 0x88; +pub const I64ROTL: u8 = 0x89; +pub const I64ROTR: u8 = 0x8a; +pub const F32ABS: u8 = 0x8b; +pub const F32NEG: u8 = 0x8c; +pub const F32CEIL: u8 = 0x8d; +pub const F32FLOOR: u8 = 0x8e; +pub const F32TRUNC: u8 = 0x8f; +pub const F32NEAREST: u8 = 0x90; +pub const F32SQRT: u8 = 0x91; +pub const F32ADD: u8 = 0x92; +pub const F32SUB: u8 = 0x93; +pub const F32MUL: u8 = 0x94; +pub const F32DIV: u8 = 0x95; +pub const F32MIN: u8 = 0x96; +pub const F32MAX: u8 = 0x97; +pub const F32COPYSIGN: u8 = 0x98; +pub const F64ABS: u8 = 0x99; +pub const F64NEG: u8 = 0x9a; +pub const F64CEIL: u8 = 0x9b; +pub const F64FLOOR: u8 = 0x9c; +pub const F64TRUNC: u8 = 0x9d; +pub const F64NEAREST: u8 = 0x9e; +pub const F64SQRT: u8 = 0x9f; +pub const F64ADD: u8 = 0xa0; +pub const F64SUB: u8 = 0xa1; +pub const F64MUL: u8 = 0xa2; +pub const F64DIV: u8 = 0xa3; +pub const F64MIN: u8 = 0xa4; +pub const F64MAX: u8 = 0xa5; +pub const F64COPYSIGN: u8 = 0xa6; + +pub const I32WRAPI64: u8 = 0xa7; +pub const I32TRUNCSF32: u8 = 0xa8; +pub const I32TRUNCUF32: u8 = 0xa9; +pub const I32TRUNCSF64: u8 = 0xaa; +pub const I32TRUNCUF64: u8 = 0xab; +pub const I64EXTENDSI32: u8 = 0xac; +pub const I64EXTENDUI32: u8 = 0xad; +pub const I64TRUNCSF32: u8 = 0xae; +pub const I64TRUNCUF32: u8 = 0xaf; +pub const I64TRUNCSF64: u8 = 0xb0; +pub const I64TRUNCUF64: u8 = 0xb1; +pub const F32CONVERTSI32: u8 = 0xb2; +pub const F32CONVERTUI32: u8 = 0xb3; +pub const F32CONVERTSI64: u8 = 0xb4; +pub const F32CONVERTUI64: u8 = 0xb5; +pub const F32DEMOTEF64: u8 = 0xb6; +pub const F64CONVERTSI32: u8 = 0xb7; +pub const F64CONVERTUI32: u8 = 0xb8; +pub const F64CONVERTSI64: u8 = 0xb9; +pub const F64CONVERTUI64: u8 = 0xba; +pub const F64PROMOTEF32: u8 = 0xbb; + +pub const I32REINTERPRETF32: u8 = 0xbc; +pub const I64REINTERPRETF64: u8 = 0xbd; +pub const F32REINTERPRETI32: u8 = 0xbe; +pub const F64REINTERPRETI64: u8 = 0xbf; diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs new file mode 100644 index 0000000000..a82a544390 --- /dev/null +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -0,0 +1,578 @@ +use bumpalo::collections::vec::Vec; +use bumpalo::Bump; + +use super::linking::{LinkingSection, RelocationEntry, RelocationSection}; +use super::opcodes; +use super::serialize::{SerialBuffer, Serialize}; +use super::{CodeBuilder, ValueType}; + +/******************************************************************* + * + * Helpers + * + *******************************************************************/ + +#[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, +} + +pub 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_u8(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 +pub fn write_custom_section_header( + buffer: &mut T, + name: &str, +) -> SectionHeaderIndices { + buffer.append_u8(SectionId::Custom as u8); + 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 +pub 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); +} + +/// Serialize a section that is just a vector of some struct +fn serialize_vector_section( + buffer: &mut B, + section_id: SectionId, + subsections: &[T], +) { + if !subsections.is_empty() { + let header_indices = write_section_header(buffer, section_id); + subsections.serialize(buffer); + update_section_size(buffer, header_indices); + } +} + +/******************************************************************* + * + * Type section + * Deduplicated list of function type signatures + * + *******************************************************************/ + +#[derive(PartialEq, Eq)] +pub struct Signature<'a> { + pub param_types: Vec<'a, ValueType>, + pub ret_type: Option, +} + +impl<'a> Serialize for Signature<'a> { + fn serialize(&self, buffer: &mut T) { + buffer.append_u8(0x60); + self.param_types.serialize(buffer); + self.ret_type.serialize(buffer); + } +} + +pub struct TypeSection<'a> { + /// Private. See WasmModule::add_function_signature + signatures: Vec<'a, Signature<'a>>, +} + +impl<'a> TypeSection<'a> { + pub fn new(arena: &'a Bump) -> Self { + TypeSection { + signatures: Vec::with_capacity_in(8, arena), + } + } + + /// Find a matching signature or insert a new one. Return the index. + fn insert(&mut self, signature: Signature<'a>) -> u32 { + // Using linear search because we need to preserve indices stored in + // the Function section. (Also for practical sizes it's fast) + let maybe_index = self.signatures.iter().position(|s| *s == signature); + match maybe_index { + Some(index) => index as u32, + None => { + let index = self.signatures.len(); + self.signatures.push(signature); + index as u32 + } + } + } +} + +impl<'a> Serialize for TypeSection<'a> { + fn serialize(&self, buffer: &mut T) { + serialize_vector_section(buffer, SectionId::Type, &self.signatures); + } +} + +/******************************************************************* + * + * Import section + * + *******************************************************************/ + +#[repr(u8)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum RefType { + Func = 0x70, + Extern = 0x6f, +} + +pub struct TableType { + pub ref_type: RefType, + pub limits: Limits, +} + +impl Serialize for TableType { + fn serialize(&self, buffer: &mut T) { + buffer.append_u8(self.ref_type as u8); + self.limits.serialize(buffer); + } +} + +pub enum ImportDesc { + Func { signature_index: u32 }, + Table { ty: TableType }, + Mem { limits: Limits }, + Global { ty: GlobalType }, +} + +pub struct Import { + pub module: String, + pub name: String, + pub description: ImportDesc, +} + +impl Serialize for Import { + fn serialize(&self, buffer: &mut T) { + self.module.serialize(buffer); + self.name.serialize(buffer); + match &self.description { + ImportDesc::Func { signature_index } => { + buffer.append_u8(0); + buffer.encode_u32(*signature_index); + } + ImportDesc::Table { ty } => { + buffer.append_u8(1); + ty.serialize(buffer); + } + ImportDesc::Mem { limits } => { + buffer.append_u8(2); + limits.serialize(buffer); + } + ImportDesc::Global { ty } => { + buffer.append_u8(3); + ty.serialize(buffer); + } + } + } +} + +pub struct ImportSection<'a> { + entries: Vec<'a, Import>, +} + +impl<'a> ImportSection<'a> { + pub fn new(arena: &'a Bump) -> Self { + ImportSection { + entries: bumpalo::vec![in arena], + } + } +} + +impl<'a> Serialize for ImportSection<'a> { + fn serialize(&self, buffer: &mut T) { + serialize_vector_section(buffer, SectionId::Import, &self.entries); + } +} + +/******************************************************************* + * + * Function section + * Maps function indices (Code section) to signature indices (Type section) + * + *******************************************************************/ + +pub struct FunctionSection<'a> { + /// Private. See WasmModule::add_function_signature + signature_indices: Vec<'a, u32>, +} + +impl<'a> FunctionSection<'a> { + pub fn new(arena: &'a Bump) -> Self { + FunctionSection { + signature_indices: Vec::with_capacity_in(8, arena), + } + } +} + +impl<'a> Serialize for FunctionSection<'a> { + fn serialize(&self, buffer: &mut T) { + serialize_vector_section(buffer, SectionId::Function, &self.signature_indices); + } +} + +/******************************************************************* + * + * Memory section + * + *******************************************************************/ + +pub enum Limits { + Min(u32), + MinMax(u32, u32), +} + +impl Serialize for Limits { + fn serialize(&self, buffer: &mut T) { + match self { + Self::Min(min) => { + buffer.append_u8(0); + buffer.encode_u32(*min); + } + Self::MinMax(min, max) => { + buffer.append_u8(1); + buffer.encode_u32(*min); + buffer.encode_u32(*max); + } + } + } +} + +pub struct MemorySection(Option); + +impl MemorySection { + pub const PAGE_SIZE: u32 = 64 * 1024; + + pub fn new(bytes: u32) -> Self { + if bytes == 0 { + MemorySection(None) + } else { + let pages = (bytes + Self::PAGE_SIZE - 1) / Self::PAGE_SIZE; + MemorySection(Some(Limits::Min(pages))) + } + } + + pub fn min_size(&self) -> Option { + match self { + MemorySection(Some(Limits::Min(min))) | MemorySection(Some(Limits::MinMax(min, _))) => { + Some(min * Self::PAGE_SIZE) + } + MemorySection(None) => None, + } + } +} + +impl Serialize for MemorySection { + fn serialize(&self, buffer: &mut T) { + if let Some(limits) = &self.0 { + let header_indices = write_section_header(buffer, SectionId::Memory); + buffer.append_u8(1); + limits.serialize(buffer); + update_section_size(buffer, header_indices); + } + } +} + +/******************************************************************* + * + * Global section + * + *******************************************************************/ + +pub struct GlobalType { + pub value_type: ValueType, + pub is_mutable: bool, +} + +impl Serialize for GlobalType { + fn serialize(&self, buffer: &mut T) { + buffer.append_u8(self.value_type as u8); + buffer.append_u8(self.is_mutable as u8); + } +} + +pub enum GlobalInitValue { + I32(i32), + I64(i64), + F32(f32), + F64(f64), +} + +pub struct Global { + pub ty: GlobalType, + pub init_value: GlobalInitValue, +} + +impl Serialize for Global { + fn serialize(&self, buffer: &mut T) { + self.ty.serialize(buffer); + match self.init_value { + GlobalInitValue::I32(x) => { + buffer.append_u8(opcodes::I32CONST); + buffer.encode_i32(x); + } + GlobalInitValue::I64(x) => { + buffer.append_u8(opcodes::I64CONST); + buffer.encode_i64(x); + } + GlobalInitValue::F32(x) => { + buffer.append_u8(opcodes::F32CONST); + buffer.encode_f32(x); + } + GlobalInitValue::F64(x) => { + buffer.append_u8(opcodes::F64CONST); + buffer.encode_f64(x); + } + } + buffer.append_u8(opcodes::END); + } +} + +pub struct GlobalSection<'a> { + pub entries: Vec<'a, Global>, +} + +impl<'a> GlobalSection<'a> { + pub fn new(arena: &'a Bump) -> Self { + GlobalSection { + entries: Vec::with_capacity_in(1, arena), + } + } +} + +impl<'a> Serialize for GlobalSection<'a> { + fn serialize(&self, buffer: &mut T) { + serialize_vector_section(buffer, SectionId::Global, &self.entries); + } +} + +/******************************************************************* + * + * Export section + * + *******************************************************************/ + +#[repr(u8)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum ExportType { + Func = 0, + Table = 1, + Mem = 2, + Global = 3, +} + +pub struct Export { + pub name: String, + pub ty: ExportType, + pub index: u32, +} +impl Serialize for Export { + fn serialize(&self, buffer: &mut T) { + self.name.serialize(buffer); + buffer.append_u8(self.ty as u8); + buffer.encode_u32(self.index); + } +} + +pub struct ExportSection<'a> { + pub entries: Vec<'a, Export>, +} + +impl<'a> ExportSection<'a> { + pub fn new(arena: &'a Bump) -> Self { + ExportSection { + entries: bumpalo::vec![in arena], + } + } +} + +impl<'a> Serialize for ExportSection<'a> { + fn serialize(&self, buffer: &mut T) { + serialize_vector_section(buffer, SectionId::Export, &self.entries); + } +} + +/******************************************************************* + * + * Code section (see also code_builder.rs) + * + *******************************************************************/ + +#[derive(Debug)] +pub struct CodeSection<'a> { + pub code_builders: Vec<'a, CodeBuilder<'a>>, +} + +impl<'a> CodeSection<'a> { + pub fn new(arena: &'a Bump) -> Self { + CodeSection { + code_builders: Vec::with_capacity_in(8, arena), + } + } + + /// Serialize the code builders for all functions, and get code relocations with final offsets + pub fn serialize_mut( + &mut self, + buffer: &mut T, + relocations: &mut Vec<'a, RelocationEntry>, + ) { + let header_indices = write_section_header(buffer, SectionId::Code); + buffer.encode_u32(self.code_builders.len() as u32); + + for code_builder in self.code_builders.iter_mut() { + code_builder.serialize_with_relocs(buffer, relocations, header_indices.body_index); + } + + update_section_size(buffer, header_indices); + } +} + +/******************************************************************* + * + * Module + * + * https://webassembly.github.io/spec/core/binary/modules.html + * + *******************************************************************/ +pub struct WasmModule<'a> { + pub types: TypeSection<'a>, + pub import: ImportSection<'a>, + pub function: FunctionSection<'a>, + /// Dummy placeholder for tables (used for function pointers and host references) + pub table: (), + pub memory: MemorySection, + pub global: GlobalSection<'a>, + pub export: ExportSection<'a>, + /// Dummy placeholder for start function. In Roc, this would be part of the platform. + pub start: (), + /// Dummy placeholder for table elements. Roc does not use tables. + pub element: (), + /// Dummy placeholder for data count section, not yet implemented + pub data_count: (), + pub code: CodeSection<'a>, + /// Dummy placeholder for data section, not yet implemented + pub data: (), + pub linking: LinkingSection<'a>, + pub reloc_code: RelocationSection<'a>, + pub reloc_data: RelocationSection<'a>, +} + +impl<'a> WasmModule<'a> { + pub const WASM_VERSION: u32 = 1; + + pub fn new(arena: &'a Bump) -> Self { + WasmModule { + types: TypeSection::new(arena), + import: ImportSection::new(arena), + function: FunctionSection::new(arena), + table: (), // Unused in Roc (mainly for function pointers) + memory: MemorySection::new(1024 * 1024), + global: GlobalSection::new(arena), + export: ExportSection::new(arena), + start: (), // Entry function. In Roc this would be part of the platform. + element: (), // Unused in Roc (related to table section) + data_count: (), // TODO, related to data section + code: CodeSection::new(arena), + data: (), // TODO: program constants (e.g. string literals) + linking: LinkingSection::new(arena), + reloc_code: RelocationSection::new(arena, "reloc.CODE"), + reloc_data: RelocationSection::new(arena, "reloc.DATA"), + } + } + + /// Create entries in the Type and Function sections for a function signature + pub fn add_function_signature(&mut self, signature: Signature<'a>) { + let index = self.types.insert(signature); + self.function.signature_indices.push(index); + } + + #[allow(clippy::unit_arg)] + pub fn serialize(&mut self, buffer: &mut T) { + buffer.append_u8(0); + buffer.append_slice("asm".as_bytes()); + buffer.write_unencoded_u32(Self::WASM_VERSION); + + let mut index: u32 = 0; + let mut prev_size = buffer.size(); + + self.types.serialize(buffer); + maybe_increment_section(buffer.size(), &mut prev_size, &mut index); + + self.import.serialize(buffer); + maybe_increment_section(buffer.size(), &mut prev_size, &mut index); + + self.function.serialize(buffer); + maybe_increment_section(buffer.size(), &mut prev_size, &mut index); + + self.table.serialize(buffer); + maybe_increment_section(buffer.size(), &mut prev_size, &mut index); + + self.memory.serialize(buffer); + maybe_increment_section(buffer.size(), &mut prev_size, &mut index); + + self.global.serialize(buffer); + maybe_increment_section(buffer.size(), &mut prev_size, &mut index); + + self.export.serialize(buffer); + maybe_increment_section(buffer.size(), &mut prev_size, &mut index); + + self.start.serialize(buffer); + maybe_increment_section(buffer.size(), &mut prev_size, &mut index); + + self.element.serialize(buffer); + maybe_increment_section(buffer.size(), &mut prev_size, &mut index); + + self.data_count.serialize(buffer); + maybe_increment_section(buffer.size(), &mut prev_size, &mut index); + + self.reloc_code.target_section_index = Some(index); + self.code + .serialize_mut(buffer, &mut self.reloc_code.entries); + maybe_increment_section(buffer.size(), &mut prev_size, &mut index); + + self.data.serialize(buffer); + self.reloc_data.target_section_index = Some(index); + + self.linking.serialize(buffer); + self.reloc_code.serialize(buffer); + self.reloc_data.serialize(buffer); + } +} + +fn maybe_increment_section(size: usize, prev_size: &mut usize, index: &mut u32) { + if size > *prev_size { + *index += 1; + *prev_size = size; + } +} diff --git a/compiler/gen_wasm/src/wasm_module/serialize.rs b/compiler/gen_wasm/src/wasm_module/serialize.rs new file mode 100644 index 0000000000..a462a96981 --- /dev/null +++ b/compiler/gen_wasm/src/wasm_module/serialize.rs @@ -0,0 +1,395 @@ +use std::fmt::Debug; + +use bumpalo::collections::vec::Vec; + +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()); + } +} + +impl Serialize for u32 { + fn serialize(&self, buffer: &mut T) { + buffer.encode_u32(*self); + } +} + +// Unit is used as a placeholder in parts of the Wasm spec we don't use yet +impl Serialize for () { + fn serialize(&self, _buffer: &mut T) {} +} + +impl Serialize for [S] { + fn serialize(&self, buffer: &mut T) { + buffer.encode_u32(self.len() as u32); + for item in self.iter() { + item.serialize(buffer); + } + } +} + +impl Serialize for Option { + /// serialize Option as a vector of length 1 or 0 + fn serialize(&self, buffer: &mut T) { + match self { + Some(x) => { + buffer.append_u8(1); + x.serialize(buffer); + } + None => { + buffer.append_u8(0); + } + } + } +} + +/// 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_u8(0x80 | ((x & 0x7f) as u8)); + x >>= 7; + } + self.append_u8(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_u8(byte); + break; + } + self.append_u8(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_u8((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_u8(0x80 | (x & 0x7f) as u8); + x >>= 7; + } + self.append_u8((x & 0x7f) as u8); + } + }; +} + +pub trait SerialBuffer: Debug { + fn append_u8(&mut self, b: u8); + fn overwrite_u8(&mut self, index: usize, 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); +} + +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_u8(&mut self, b: u8) { + self.push(b); + } + fn overwrite_u8(&mut self, index: usize, b: u8) { + self[index] = 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_u8(&mut self, b: u8) { + self.push(b); + } + fn overwrite_u8(&mut self, index: usize, b: u8) { + self[index] = 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/helpers/wasm32_test_result.rs b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs deleted file mode 100644 index 5cf709a597..0000000000 --- a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs +++ /dev/null @@ -1,278 +0,0 @@ -use parity_wasm::builder; -use parity_wasm::builder::ModuleBuilder; -use parity_wasm::elements::{ - Instruction, Instruction::*, Instructions, Internal, Local, ValueType, -}; - -use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory; -use roc_gen_wasm::*; -use roc_std::{RocDec, RocList, RocOrder, RocStr}; - -const STACK_POINTER_LOCAL_ID: u32 = 0; - -pub trait Wasm32TestResult { - fn insert_test_wrapper( - module_builder: &mut ModuleBuilder, - wrapper_name: &str, - main_function_index: u32, - ) { - let instructions = Self::build_wrapper_body(main_function_index); - - let signature = builder::signature().with_result(ValueType::I32).build_sig(); - - let stack_frame_pointer = Local::new(1, ValueType::I32); - let function_def = builder::function() - .with_signature(signature) - .body() - .with_locals(vec![stack_frame_pointer]) - .with_instructions(Instructions::new(instructions)) - .build() // body - .build(); // function - - let location = module_builder.push_function(function_def); - let export = builder::export() - .field(wrapper_name) - .with_internal(Internal::Function(location.body)) - .build(); - - module_builder.push_export(export); - } - - fn build_wrapper_body(main_function_index: u32) -> Vec; -} - -macro_rules! build_wrapper_body_primitive { - ($store_instruction: expr, $align: expr) => { - fn build_wrapper_body(main_function_index: u32) -> Vec { - let size: i32 = 8; - let mut instructions = Vec::with_capacity(16); - push_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID)); - instructions.extend([ - // load result address to prepare for the store instruction later - GetLocal(STACK_POINTER_LOCAL_ID), - // - // Call the main function with no arguments. Get primitive back. - Call(main_function_index), - // - // Store the primitive at the allocated address - $store_instruction($align, 0), - // - // Return the result pointer - GetLocal(STACK_POINTER_LOCAL_ID), - ]); - pop_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID)); - instructions.push(End); - instructions - } - }; -} - -macro_rules! wasm_test_result_primitive { - ($type_name: ident, $store_instruction: expr, $align: expr) => { - impl Wasm32TestResult for $type_name { - build_wrapper_body_primitive!($store_instruction, $align); - } - }; -} - -fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec { - let mut instructions = Vec::with_capacity(16); - push_stack_frame( - &mut instructions, - size as i32, - LocalId(STACK_POINTER_LOCAL_ID), - ); - instructions.extend([ - // - // Call the main function with the allocated address to write the result. - // No value is returned to the VM stack. This is the same as in compiled C. - GetLocal(STACK_POINTER_LOCAL_ID), - Call(main_function_index), - // - // Return the result address - GetLocal(STACK_POINTER_LOCAL_ID), - ]); - pop_stack_frame( - &mut instructions, - size as i32, - LocalId(STACK_POINTER_LOCAL_ID), - ); - instructions.push(End); - instructions -} - -macro_rules! wasm_test_result_stack_memory { - ($type_name: ident) => { - impl Wasm32TestResult for $type_name { - fn build_wrapper_body(main_function_index: u32) -> Vec { - build_wrapper_body_stack_memory(main_function_index, $type_name::ACTUAL_WIDTH) - } - } - }; -} - -wasm_test_result_primitive!(bool, I32Store8, ALIGN_1); -wasm_test_result_primitive!(RocOrder, I32Store8, ALIGN_1); - -wasm_test_result_primitive!(u8, I32Store8, ALIGN_1); -wasm_test_result_primitive!(i8, I32Store8, ALIGN_1); -wasm_test_result_primitive!(u16, I32Store16, ALIGN_2); -wasm_test_result_primitive!(i16, I32Store16, ALIGN_2); -wasm_test_result_primitive!(u32, I32Store, ALIGN_4); -wasm_test_result_primitive!(i32, I32Store, ALIGN_4); -wasm_test_result_primitive!(u64, I64Store, ALIGN_8); -wasm_test_result_primitive!(i64, I64Store, ALIGN_8); - -wasm_test_result_primitive!(f32, F32Store, ALIGN_8); -wasm_test_result_primitive!(f64, F64Store, ALIGN_8); - -wasm_test_result_stack_memory!(u128); -wasm_test_result_stack_memory!(i128); -wasm_test_result_stack_memory!(RocDec); -wasm_test_result_stack_memory!(RocStr); - -impl Wasm32TestResult for RocList { - fn build_wrapper_body(main_function_index: u32) -> Vec { - build_wrapper_body_stack_memory(main_function_index, 12) - } -} - -impl Wasm32TestResult for &'_ T { - build_wrapper_body_primitive!(I32Store, ALIGN_4); -} - -impl Wasm32TestResult for [T; N] -where - T: Wasm32TestResult + FromWasm32Memory, -{ - fn build_wrapper_body(main_function_index: u32) -> Vec { - build_wrapper_body_stack_memory(main_function_index, N * T::ACTUAL_WIDTH) - } -} - -impl Wasm32TestResult for (T, U) -where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, -{ - fn build_wrapper_body(main_function_index: u32) -> Vec { - build_wrapper_body_stack_memory(main_function_index, T::ACTUAL_WIDTH + U::ACTUAL_WIDTH) - } -} - -impl Wasm32TestResult for (T, U, V) -where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, - V: Wasm32TestResult + FromWasm32Memory, -{ - fn build_wrapper_body(main_function_index: u32) -> Vec { - build_wrapper_body_stack_memory( - main_function_index, - T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH, - ) - } -} - -impl Wasm32TestResult for (T, U, V, W) -where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, - V: Wasm32TestResult + FromWasm32Memory, - W: Wasm32TestResult + FromWasm32Memory, -{ - fn build_wrapper_body(main_function_index: u32) -> Vec { - build_wrapper_body_stack_memory( - main_function_index, - T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH, - ) - } -} - -impl Wasm32TestResult for (T, U, V, W, X) -where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, - V: Wasm32TestResult + FromWasm32Memory, - W: Wasm32TestResult + FromWasm32Memory, - X: Wasm32TestResult + FromWasm32Memory, -{ - fn build_wrapper_body(main_function_index: u32) -> Vec { - build_wrapper_body_stack_memory( - main_function_index, - T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH + X::ACTUAL_WIDTH, - ) - } -} - -impl Wasm32TestResult for (T, U, V, W, X, Y) -where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, - V: Wasm32TestResult + FromWasm32Memory, - W: Wasm32TestResult + FromWasm32Memory, - X: Wasm32TestResult + FromWasm32Memory, - Y: Wasm32TestResult + FromWasm32Memory, -{ - fn build_wrapper_body(main_function_index: u32) -> Vec { - build_wrapper_body_stack_memory( - main_function_index, - T::ACTUAL_WIDTH - + U::ACTUAL_WIDTH - + V::ACTUAL_WIDTH - + W::ACTUAL_WIDTH - + X::ACTUAL_WIDTH - + Y::ACTUAL_WIDTH, - ) - } -} - -impl Wasm32TestResult for (T, U, V, W, X, Y, Z) -where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, - V: Wasm32TestResult + FromWasm32Memory, - W: Wasm32TestResult + FromWasm32Memory, - X: Wasm32TestResult + FromWasm32Memory, - Y: Wasm32TestResult + FromWasm32Memory, - Z: Wasm32TestResult + FromWasm32Memory, -{ - fn build_wrapper_body(main_function_index: u32) -> Vec { - build_wrapper_body_stack_memory( - main_function_index, - T::ACTUAL_WIDTH - + U::ACTUAL_WIDTH - + V::ACTUAL_WIDTH - + W::ACTUAL_WIDTH - + X::ACTUAL_WIDTH - + Y::ACTUAL_WIDTH - + Z::ACTUAL_WIDTH, - ) - } -} - -impl Wasm32TestResult for (T, U, V, W, X, Y, Z, A) -where - T: Wasm32TestResult + FromWasm32Memory, - U: Wasm32TestResult + FromWasm32Memory, - V: Wasm32TestResult + FromWasm32Memory, - W: Wasm32TestResult + FromWasm32Memory, - X: Wasm32TestResult + FromWasm32Memory, - Y: Wasm32TestResult + FromWasm32Memory, - Z: Wasm32TestResult + FromWasm32Memory, - A: Wasm32TestResult + FromWasm32Memory, -{ - fn build_wrapper_body(main_function_index: u32) -> Vec { - build_wrapper_body_stack_memory( - main_function_index, - T::ACTUAL_WIDTH - + U::ACTUAL_WIDTH - + V::ACTUAL_WIDTH - + W::ACTUAL_WIDTH - + X::ACTUAL_WIDTH - + Y::ACTUAL_WIDTH - + Z::ACTUAL_WIDTH - + A::ACTUAL_WIDTH, - ) - } -} 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/ident/src/lib.rs b/compiler/ident/src/lib.rs index 40a10200a6..97c9a18785 100644 --- a/compiler/ident/src/lib.rs +++ b/compiler/ident/src/lib.rs @@ -231,9 +231,9 @@ impl fmt::Debug for IdentStr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // IdentStr { is_small_str: false, storage: Refcounted(3), elements: [ 1,2,3,4] } f.debug_struct("IdentStr") - .field("is_small_str", &self.is_small_str()) + //.field("is_small_str", &self.is_small_str()) .field("string", &self.as_str()) - .field("elements", &self.as_slice()) + //.field("elements", &self.as_slice()) .finish() } } diff --git a/compiler/load/src/effect_module.rs b/compiler/load/src/effect_module.rs index f6081e6e21..eeea64d7a1 100644 --- a/compiler/load/src/effect_module.rs +++ b/compiler/load/src/effect_module.rs @@ -1,7 +1,7 @@ use roc_can::annotation::IntroducedVariables; use roc_can::def::{Declaration, Def}; use roc_can::env::Env; -use roc_can::expr::{Expr, Recursive}; +use roc_can::expr::{ClosureData, Expr, Recursive}; use roc_can::pattern::Pattern; use roc_can::scope::Scope; use roc_collections::all::{MutSet, SendMap}; @@ -117,7 +117,7 @@ fn build_effect_always( let body = Expr::Var(value_symbol); - Expr::Closure { + Expr::Closure(ClosureData { function_type: var_store.fresh(), closure_type: var_store.fresh(), closure_ext_var: var_store.fresh(), @@ -127,7 +127,7 @@ fn build_effect_always( recursive: Recursive::NotRecursive, arguments, loc_body: Box::new(Located::at_zero(body)), - } + }) }; // \value -> @Effect \{} -> value @@ -146,7 +146,7 @@ fn build_effect_always( )]; let function_var = var_store.fresh(); - let closure = Expr::Closure { + let closure = Expr::Closure(ClosureData { function_type: function_var, closure_type: var_store.fresh(), closure_ext_var: var_store.fresh(), @@ -156,7 +156,7 @@ fn build_effect_always( recursive: Recursive::NotRecursive, arguments, loc_body: Box::new(Located::at_zero(body)), - }; + }); (function_var, closure) }; @@ -295,7 +295,7 @@ fn build_effect_map( Located::at_zero(empty_record_pattern(var_store)), )]; - Expr::Closure { + Expr::Closure(ClosureData { function_type: var_store.fresh(), closure_type: var_store.fresh(), closure_ext_var: var_store.fresh(), @@ -308,7 +308,7 @@ fn build_effect_map( recursive: Recursive::NotRecursive, arguments, loc_body: Box::new(Located::at_zero(mapper_call)), - } + }) }; let arguments = vec![ @@ -339,7 +339,7 @@ fn build_effect_map( }; let function_var = var_store.fresh(); - let map_closure = Expr::Closure { + let map_closure = Expr::Closure(ClosureData { function_type: function_var, closure_type: var_store.fresh(), closure_ext_var: var_store.fresh(), @@ -349,7 +349,7 @@ fn build_effect_map( recursive: Recursive::NotRecursive, arguments, loc_body: Box::new(Located::at_zero(body)), - }; + }); let mut introduced_variables = IntroducedVariables::default(); @@ -509,7 +509,7 @@ fn build_effect_after( ]; let function_var = var_store.fresh(); - let after_closure = Expr::Closure { + let after_closure = Expr::Closure(ClosureData { function_type: function_var, closure_type: var_store.fresh(), closure_ext_var: var_store.fresh(), @@ -519,7 +519,7 @@ fn build_effect_after( recursive: Recursive::NotRecursive, arguments, loc_body: Box::new(Located::at_zero(to_effect_call)), - }; + }); let mut introduced_variables = IntroducedVariables::default(); @@ -653,7 +653,7 @@ pub fn build_host_exposed_def( .unwrap() }; - let effect_closure = Expr::Closure { + let effect_closure = Expr::Closure(ClosureData { function_type: var_store.fresh(), closure_type: var_store.fresh(), closure_ext_var: var_store.fresh(), @@ -666,7 +666,7 @@ pub fn build_host_exposed_def( Located::at_zero(empty_record_pattern(var_store)), )], loc_body: Box::new(Located::at_zero(low_level_call)), - }; + }); let body = Expr::Tag { variant_var: var_store.fresh(), @@ -675,7 +675,7 @@ pub fn build_host_exposed_def( arguments: vec![(var_store.fresh(), Located::at_zero(effect_closure))], }; - Expr::Closure { + Expr::Closure(ClosureData { function_type: var_store.fresh(), closure_type: var_store.fresh(), closure_ext_var: var_store.fresh(), @@ -685,7 +685,7 @@ pub fn build_host_exposed_def( recursive: Recursive::NotRecursive, arguments, loc_body: Box::new(Located::at_zero(body)), - } + }) } _ => { // not a function @@ -717,7 +717,7 @@ pub fn build_host_exposed_def( destructs: vec![], }; - let effect_closure = Expr::Closure { + let effect_closure = Expr::Closure(ClosureData { function_type: var_store.fresh(), closure_type: var_store.fresh(), closure_ext_var: var_store.fresh(), @@ -727,7 +727,7 @@ pub fn build_host_exposed_def( recursive: Recursive::NotRecursive, arguments: vec![(var_store.fresh(), Located::at_zero(empty_record_pattern))], loc_body: Box::new(Located::at_zero(low_level_call)), - }; + }); Expr::Tag { variant_var: var_store.fresh(), diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index ad541252f7..60e8d7a5f2 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -8,7 +8,7 @@ use roc_builtins::std::StdLib; use roc_can::constraint::Constraint; use roc_can::def::{Declaration, Def}; use roc_can::module::{canonicalize_module_defs, Module}; -use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, BumpSet, MutMap, MutSet}; +use roc_collections::all::{default_hasher, BumpMap, MutMap, MutSet}; use roc_constrain::module::{ constrain_imports, pre_constrain_imports, ConstrainableImports, Import, }; @@ -553,7 +553,7 @@ fn start_phase<'a>( ident_ids, } = typechecked; - let mut imported_module_thunks = BumpSet::new_in(arena); + let mut imported_module_thunks = bumpalo::collections::Vec::new_in(arena); if let Some(imports) = state.module_cache.imports.get(&module_id) { for imported in imports.iter() { @@ -570,7 +570,7 @@ fn start_phase<'a>( module_id, module_timing, solved_subs, - imported_module_thunks, + imported_module_thunks: imported_module_thunks.into_bump_slice(), decls, ident_ids, exposed_to_host: state.exposed_to_host.clone(), @@ -593,7 +593,7 @@ fn start_phase<'a>( module_id, ident_ids, subs, - procs, + procs_base, layout_cache, module_timing, } = found_specializations; @@ -602,7 +602,7 @@ fn start_phase<'a>( module_id, ident_ids, subs, - procs, + procs_base, layout_cache, specializations_we_must_make, module_timing, @@ -646,6 +646,13 @@ impl LoadedModule { total } + + pub fn exposed_values_str(&self) -> Vec<&str> { + self.exposed_values + .iter() + .map(|symbol| symbol.ident_str(&self.interns).as_str()) + .collect() + } } #[derive(Debug)] @@ -710,13 +717,13 @@ pub struct TypeCheckedModule<'a> { } #[derive(Debug)] -pub struct FoundSpecializationsModule<'a> { - pub module_id: ModuleId, - pub ident_ids: IdentIds, - pub layout_cache: LayoutCache<'a>, - pub procs: Procs<'a>, - pub subs: Subs, - pub module_timing: ModuleTiming, +struct FoundSpecializationsModule<'a> { + module_id: ModuleId, + ident_ids: IdentIds, + layout_cache: LayoutCache<'a>, + procs_base: ProcsBase<'a>, + subs: Subs, + module_timing: ModuleTiming, } #[derive(Debug)] @@ -815,7 +822,7 @@ enum Msg<'a> { module_id: ModuleId, ident_ids: IdentIds, layout_cache: LayoutCache<'a>, - procs: Procs<'a>, + procs_base: ProcsBase<'a>, problems: Vec, solved_subs: Solved, module_timing: ModuleTiming, @@ -997,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() } } @@ -1035,7 +1042,7 @@ enum BuildTask<'a> { module_timing: ModuleTiming, layout_cache: LayoutCache<'a>, solved_subs: Solved, - imported_module_thunks: BumpSet, + imported_module_thunks: &'a [Symbol], module_id: ModuleId, ident_ids: IdentIds, decls: Vec, @@ -1045,7 +1052,7 @@ enum BuildTask<'a> { module_id: ModuleId, ident_ids: IdentIds, subs: Subs, - procs: Procs<'a>, + procs_base: ProcsBase<'a>, layout_cache: LayoutCache<'a>, specializations_we_must_make: ExternalSpecializations<'a>, module_timing: ModuleTiming, @@ -2050,7 +2057,7 @@ fn update<'a>( } FoundSpecializations { module_id, - procs, + procs_base, solved_subs, ident_ids, layout_cache, @@ -2060,16 +2067,14 @@ fn update<'a>( log!("found specializations for {:?}", module_id); let subs = solved_subs.into_inner(); - if let Some(pending) = &procs.pending_specializations { - for (symbol, specs) in pending { - let existing = match state.all_pending_specializations.entry(*symbol) { - Vacant(entry) => entry.insert(MutMap::default()), - Occupied(entry) => entry.into_mut(), - }; + for (symbol, specs) in &procs_base.specializations_for_host { + let existing = match state.all_pending_specializations.entry(*symbol) { + Vacant(entry) => entry.insert(MutMap::default()), + Occupied(entry) => entry.into_mut(), + }; - for (layout, pend) in specs { - existing.insert(*layout, pend.clone()); - } + for (layout, pend) in specs { + existing.insert(*layout, pend.clone()); } } @@ -2078,13 +2083,13 @@ fn update<'a>( .top_level_thunks .entry(module_id) .or_default() - .extend(procs.module_thunks.iter().copied()); + .extend(procs_base.module_thunks.iter().copied()); let found_specializations_module = FoundSpecializationsModule { module_id, ident_ids, layout_cache, - procs, + procs_base, subs, module_timing, }; @@ -3930,7 +3935,7 @@ fn make_specializations<'a>( home: ModuleId, mut ident_ids: IdentIds, mut subs: Subs, - mut procs: Procs<'a>, + procs_base: ProcsBase<'a>, mut layout_cache: LayoutCache<'a>, specializations_we_must_make: ExternalSpecializations<'a>, mut module_timing: ModuleTiming, @@ -3951,6 +3956,16 @@ fn make_specializations<'a>( call_specialization_counter: 1, }; + let mut procs = Procs::new_in(arena); + + for (symbol, partial_proc) in procs_base.partial_procs.into_iter() { + procs.partial_procs.insert(symbol, partial_proc); + } + + procs.module_thunks = procs_base.module_thunks; + procs.runtime_errors = procs_base.runtime_errors; + procs.imported_module_thunks = procs_base.imported_module_thunks; + // TODO: for now this final specialization pass is sequential, // with no parallelization at all. We should try to parallelize // this, but doing so will require a redesign of Procs. @@ -3958,12 +3973,16 @@ fn make_specializations<'a>( &mut mono_env, procs, specializations_we_must_make, + procs_base.specializations_for_host, &mut layout_cache, ); let external_specializations_requested = procs.externals_we_need.clone(); let procedures = procs.get_specialized_procs_without_rc(&mut mono_env); + // Turn `Bytes.Decode.IdentId(238)` into `Bytes.Decode.238`, we rely on this in mono tests + mono_env.home.register_debug_idents(mono_env.ident_ids); + let make_specializations_end = SystemTime::now(); module_timing.make_specializations = make_specializations_end .duration_since(make_specializations_start) @@ -3981,11 +4000,37 @@ fn make_specializations<'a>( } } +#[derive(Clone, Debug)] +struct ProcsBase<'a> { + partial_procs: BumpMap>, + module_thunks: &'a [Symbol], + /// A host-exposed function must be specialized; it's a seed for subsequent specializations + specializations_for_host: BumpMap, PendingSpecialization<'a>>>, + runtime_errors: BumpMap, + imported_module_thunks: &'a [Symbol], +} + +impl<'a> ProcsBase<'a> { + fn add_specialization_for_host( + &mut self, + symbol: Symbol, + layout: ProcLayout<'a>, + pending: PendingSpecialization<'a>, + ) { + let all_pending = self + .specializations_for_host + .entry(symbol) + .or_insert_with(|| HashMap::with_capacity_and_hasher(1, default_hasher())); + + all_pending.insert(layout, pending); + } +} + #[allow(clippy::too_many_arguments)] fn build_pending_specializations<'a>( arena: &'a Bump, solved_subs: Solved, - imported_module_thunks: BumpSet, + imported_module_thunks: &'a [Symbol], home: ModuleId, mut ident_ids: IdentIds, decls: Vec, @@ -3996,10 +4041,16 @@ fn build_pending_specializations<'a>( exposed_to_host: MutMap, ) -> Msg<'a> { let find_specializations_start = SystemTime::now(); - let mut procs = Procs::new_in(arena); - debug_assert!(procs.imported_module_thunks.is_empty()); - procs.imported_module_thunks = imported_module_thunks; + let mut module_thunks = bumpalo::collections::Vec::new_in(arena); + + let mut procs_base = ProcsBase { + partial_procs: BumpMap::default(), + module_thunks: &[], + specializations_for_host: BumpMap::default(), + runtime_errors: BumpMap::default(), + imported_module_thunks, + }; let mut mono_problems = std::vec::Vec::new(); let mut subs = solved_subs.into_inner(); @@ -4022,7 +4073,8 @@ fn build_pending_specializations<'a>( match decl { Declare(def) | Builtin(def) => add_def_to_module( &mut layout_cache, - &mut procs, + &mut procs_base, + &mut module_thunks, &mut mono_env, def, &exposed_to_host, @@ -4032,7 +4084,8 @@ fn build_pending_specializations<'a>( for def in defs { add_def_to_module( &mut layout_cache, - &mut procs, + &mut procs_base, + &mut module_thunks, &mut mono_env, def, &exposed_to_host, @@ -4047,6 +4100,8 @@ fn build_pending_specializations<'a>( } } + procs_base.module_thunks = module_thunks.into_bump_slice(); + let problems = mono_env.problems.to_vec(); let find_specializations_end = SystemTime::now(); @@ -4059,7 +4114,7 @@ fn build_pending_specializations<'a>( solved_subs: roc_types::solved_types::Solved(subs), ident_ids, layout_cache, - procs, + procs_base, problems, module_timing, } @@ -4067,12 +4122,14 @@ fn build_pending_specializations<'a>( fn add_def_to_module<'a>( layout_cache: &mut LayoutCache<'a>, - procs: &mut Procs<'a>, + procs: &mut ProcsBase<'a>, + module_thunks: &mut bumpalo::collections::Vec<'a, Symbol>, mono_env: &mut roc_mono::ir::Env<'a, '_>, def: roc_can::def::Def, exposed_to_host: &MutMap, is_recursive: bool, ) { + use roc_can::expr::ClosureData; use roc_can::expr::Expr::*; use roc_can::pattern::Pattern::*; @@ -4081,14 +4138,14 @@ fn add_def_to_module<'a>( let is_exposed = exposed_to_host.contains_key(&symbol); match def.loc_expr.value { - Closure { + Closure(ClosureData { function_type: annotation, return_type: ret_var, arguments: loc_args, loc_body, captured_symbols, .. - } => { + }) => { // this is a top-level definition, it should not capture anything debug_assert!(captured_symbols.is_empty()); @@ -4097,15 +4154,6 @@ fn add_def_to_module<'a>( // never gets called by Roc code, it will never // get specialized! if is_exposed { - let mut pattern_vars = bumpalo::collections::Vec::with_capacity_in( - loc_args.len(), - mono_env.arena, - ); - - for (var, _) in loc_args.iter() { - pattern_vars.push(*var); - } - let layout = match layout_cache.raw_from_var( mono_env.arena, annotation, @@ -4129,20 +4177,23 @@ fn add_def_to_module<'a>( } }; - procs.insert_exposed( - symbol, - ProcLayout::from_raw(mono_env.arena, layout), + let pending = PendingSpecialization::from_exposed_function( mono_env.arena, mono_env.subs, def.annotation, annotation, ); + + procs.add_specialization_for_host( + symbol, + ProcLayout::from_raw(mono_env.arena, layout), + pending, + ); } - procs.insert_named( + let partial_proc = PartialProc::from_named_function( mono_env, layout_cache, - symbol, annotation, loc_args, *loc_body, @@ -4150,10 +4201,12 @@ fn add_def_to_module<'a>( is_recursive, ret_var, ); + + procs.partial_procs.insert(symbol, partial_proc); } body => { // mark this symbols as a top-level thunk before any other work on the procs - procs.module_thunks.insert(symbol); + module_thunks.push(symbol); // If this is an exposed symbol, we need to // register it as such. Otherwise, since it @@ -4188,14 +4241,14 @@ fn add_def_to_module<'a>( } }; - procs.insert_exposed( - symbol, - top_level, + let pending = PendingSpecialization::from_exposed_function( mono_env.arena, mono_env.subs, def.annotation, annotation, ); + + procs.add_specialization_for_host(symbol, top_level, pending); } let proc = PartialProc { @@ -4309,7 +4362,7 @@ where module_id, ident_ids, subs, - procs, + procs_base, layout_cache, specializations_we_must_make, module_timing, @@ -4318,7 +4371,7 @@ where module_id, ident_ids, subs, - procs, + procs_base, layout_cache, specializations_we_must_make, module_timing, @@ -4477,8 +4530,8 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin let doc = alloc.stack(vec![ alloc.reflow(r"The input file is a interface file, but only app modules can be ran."), alloc.concat(vec![ - alloc.reflow(r"I will still parse and typecheck the input file and its dependencies,"), - alloc.reflow(r"but won't output any executable."), + alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "), + alloc.reflow(r"but won't output any executable."), ]) ]); @@ -4493,8 +4546,8 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin let doc = alloc.stack(vec![ alloc.reflow(r"The input file is a package config file, but only app modules can be ran."), alloc.concat(vec![ - alloc.reflow(r"I will still parse and typecheck the input file and its dependencies,"), - alloc.reflow(r"but won't output any executable."), + alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "), + alloc.reflow(r"but won't output any executable."), ]) ]); diff --git a/compiler/module/Cargo.toml b/compiler/module/Cargo.toml index 239468c8c6..06602feab1 100644 --- a/compiler/module/Cargo.toml +++ b/compiler/module/Cargo.toml @@ -12,8 +12,8 @@ roc_collections = { path = "../collections" } bumpalo = { version = "3.6.1", features = ["collections"] } lazy_static = "1.4" static_assertions = "1.1.0" +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/ident.rs b/compiler/module/src/ident.rs index 0de36bd0bd..6f31a28751 100644 --- a/compiler/module/src/ident.rs +++ b/compiler/module/src/ident.rs @@ -4,7 +4,7 @@ use std::fmt; /// This could be uppercase or lowercase, qualified or unqualified. #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Ident(IdentStr); +pub struct Ident(pub IdentStr); impl Ident { pub fn as_inline_str(&self) -> &IdentStr { diff --git a/compiler/module/src/lib.rs b/compiler/module/src/lib.rs index 0bd93ef98f..3b0c3eea17 100644 --- a/compiler/module/src/lib.rs +++ b/compiler/module/src/lib.rs @@ -4,6 +4,7 @@ pub mod ident; pub mod low_level; +pub mod module_err; pub mod operator; pub mod symbol; diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 9be88c2945..58eea72a3b 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -17,6 +17,7 @@ pub enum LowLevel { StrToUtf8, StrRepeat, StrFromFloat, + StrTrim, ListLen, ListGetUnsafe, ListSet, @@ -32,6 +33,7 @@ pub enum LowLevel { ListMap, ListMap2, ListMap3, + ListMap4, ListMapWithIndex, ListKeepIf, ListWalk, @@ -123,6 +125,7 @@ macro_rules! first_order { | StrFromUtf8Range | StrToUtf8 | StrRepeat + | StrTrim | StrFromFloat | ListLen | ListGetUnsafe @@ -209,6 +212,7 @@ macro_rules! higher_order { ListMap | ListMap2 | ListMap3 + | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk @@ -241,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/module_err.rs b/compiler/module/src/module_err.rs new file mode 100644 index 0000000000..f2b1f0b168 --- /dev/null +++ b/compiler/module/src/module_err.rs @@ -0,0 +1,30 @@ +use snafu::{Backtrace, Snafu}; + +use crate::symbol::IdentId; + +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] +pub enum ModuleError { + #[snafu(display( + "ModuleIdNotFound: I could not find the ModuleId {} in Interns.all_ident_ids: {}.", + module_id, + all_ident_ids + ))] + ModuleIdNotFound { + module_id: String, + all_ident_ids: String, + backtrace: Backtrace, + }, + #[snafu(display( + "IdentIdNotFound: I could not find IdentId {:?} in ident_ids {:?}.", + ident_id, + ident_ids_str + ))] + IdentIdNotFound { + ident_id: IdentId, + ident_ids_str: String, + backtrace: Backtrace, + }, +} + +pub type ModuleResult = std::result::Result; diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 55aeb7e06b..79510c3700 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -1,7 +1,9 @@ use crate::ident::{Ident, ModuleName}; +use crate::module_err::{IdentIdNotFound, ModuleIdNotFound, ModuleResult}; use roc_collections::all::{default_hasher, MutMap, SendMap}; use roc_ident::IdentStr; use roc_region::all::Region; +use snafu::OptionExt; use std::collections::HashMap; use std::{fmt, u32}; @@ -253,6 +255,30 @@ impl Interns { } } +pub fn get_module_ident_ids<'a>( + all_ident_ids: &'a MutMap, + module_id: &ModuleId, +) -> ModuleResult<&'a IdentIds> { + all_ident_ids + .get(module_id) + .with_context(|| ModuleIdNotFound { + module_id: format!("{:?}", module_id), + all_ident_ids: format!("{:?}", all_ident_ids), + }) +} + +pub fn get_module_ident_ids_mut<'a>( + all_ident_ids: &'a mut MutMap, + module_id: &ModuleId, +) -> ModuleResult<&'a mut IdentIds> { + all_ident_ids + .get_mut(module_id) + .with_context(|| ModuleIdNotFound { + module_id: format!("{:?}", module_id), + all_ident_ids: "I could not return all_ident_ids here because of borrowing issues.", + }) +} + #[cfg(debug_assertions)] lazy_static! { /// This is used in Debug builds only, to let us have a Debug instance @@ -536,15 +562,17 @@ impl IdentIds { } pub fn get_or_insert(&mut self, name: &Ident) -> IdentId { - match self.by_ident.get(name) { - Some(id) => *id, - None => { + use std::collections::hash_map::Entry; + + match self.by_ident.entry(name.clone()) { + Entry::Occupied(occupied) => *occupied.get(), + Entry::Vacant(vacant) => { let by_id = &mut self.by_id; let ident_id = IdentId(by_id.len() as u32); by_id.push(name.clone()); - self.by_ident.insert(name.clone(), ident_id); + vacant.insert(ident_id); ident_id } @@ -621,6 +649,17 @@ impl IdentIds { pub fn get_name(&self, id: IdentId) -> Option<&Ident> { self.by_id.get(id.0 as usize) } + + pub fn get_name_str_res(&self, ident_id: IdentId) -> ModuleResult<&str> { + Ok(self + .get_name(ident_id) + .with_context(|| IdentIdNotFound { + ident_id, + ident_ids_str: format!("{:?}", self), + })? + .as_inline_str() + .as_str()) + } } // BUILTINS @@ -978,6 +1017,7 @@ define_builtins! { 17 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime 18 STR_FROM_UTF8_RANGE: "fromUtf8Range" 19 STR_REPEAT: "repeat" + 20 STR_TRIM: "trim" } 4 LIST: "List" => { 0 LIST_LIST: "List" imported // the List.List type alias @@ -1015,6 +1055,12 @@ define_builtins! { 32 LIST_DROP: "drop" 33 LIST_SWAP: "swap" 34 LIST_DROP_AT: "dropAt" + 35 LIST_DROP_LAST: "dropLast" + 36 LIST_MIN: "min" + 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 13485868d1..2a0d4b1c18 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -10,7 +10,8 @@ use roc_module::symbol::Symbol; use std::convert::TryFrom; use crate::ir::{ - Call, CallType, Expr, HostExposedLayouts, ListLiteralElement, Literal, ModifyRc, Proc, Stmt, + Call, CallType, Expr, HostExposedLayouts, ListLiteralElement, Literal, ModifyRc, OptLevel, + Proc, Stmt, }; use crate::layout::{Builtin, Layout, ListLayout, RawFunctionLayout, UnionLayout}; @@ -109,6 +110,7 @@ fn bytes_as_ascii(bytes: &[u8]) -> String { } pub fn spec_program<'a, I>( + opt_level: OptLevel, entry_point: crate::ir::EntryPoint<'a>, procs: I, ) -> Result @@ -239,7 +241,10 @@ where eprintln!("{}", program.to_source_string()); } - morphic_lib::solve(program) + match opt_level { + OptLevel::Development | OptLevel::Normal => morphic_lib::solve_trivial(program), + OptLevel::Optimize => morphic_lib::solve(program), + } } /// if you want an "escape hatch" which allows you construct "best-case scenario" values @@ -556,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, @@ -608,6 +691,7 @@ fn call_spec( HigherOrderLowLevel { specialization_id, closure_env_layout, + update_mode, op, arg_layouts, ret_layout, @@ -615,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, @@ -878,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)?; @@ -911,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]]; @@ -1420,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 d890c136c7..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); @@ -922,6 +937,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]), ListConcat => arena.alloc_slice_copy(&[owned, owned]), StrConcat => arena.alloc_slice_copy(&[owned, borrowed]), + StrTrim => arena.alloc_slice_copy(&[owned]), StrSplit => arena.alloc_slice_copy(&[borrowed, borrowed]), ListSingle => arena.alloc_slice_copy(&[irrelevant]), ListRepeat => arena.alloc_slice_copy(&[irrelevant, borrowed]), @@ -932,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/expand_rc.rs b/compiler/mono/src/expand_rc.rs index 5b70006c84..2812252285 100644 --- a/compiler/mono/src/expand_rc.rs +++ b/compiler/mono/src/expand_rc.rs @@ -179,16 +179,12 @@ impl<'a, 'i> Env<'a, 'i> { pub fn unique_symbol(&mut self) -> Symbol { let ident_id = self.ident_ids.gen_unique(); - self.home.register_debug_idents(self.ident_ids); - Symbol::new(self.home, ident_id) } #[allow(dead_code)] fn manual_unique_symbol(home: ModuleId, ident_ids: &mut IdentIds) -> Symbol { let ident_id = ident_ids.gen_unique(); - home.register_debug_idents(ident_ids); - Symbol::new(home, ident_id) } } 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 61a5869d46..6ef12f8814 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -8,7 +8,9 @@ use crate::layout::{ }; use bumpalo::collections::Vec; use bumpalo::Bump; -use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, BumpSet, MutMap, MutSet}; +use hashbrown::hash_map::Entry; +use roc_can::expr::ClosureData; +use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; @@ -70,6 +72,61 @@ pub struct EntryPoint<'a> { pub layout: ProcLayout<'a>, } +#[derive(Clone, Copy, Debug)] +pub struct PartialProcId(usize); + +#[derive(Clone, Debug, PartialEq)] +pub struct PartialProcs<'a> { + /// maps a function name (symbol) to an index + symbols: Vec<'a, Symbol>, + + partial_procs: Vec<'a, PartialProc<'a>>, +} + +impl<'a> PartialProcs<'a> { + fn new_in(arena: &'a Bump) -> Self { + Self { + symbols: Vec::new_in(arena), + partial_procs: Vec::new_in(arena), + } + } + fn contains_key(&self, symbol: Symbol) -> bool { + self.symbol_to_id(symbol).is_some() + } + + fn symbol_to_id(&self, symbol: Symbol) -> Option { + self.symbols + .iter() + .position(|s| *s == symbol) + .map(PartialProcId) + } + + fn get_symbol(&self, symbol: Symbol) -> Option<&PartialProc<'a>> { + let id = self.symbol_to_id(symbol)?; + + Some(self.get_id(id)) + } + + fn get_id(&self, id: PartialProcId) -> &PartialProc<'a> { + &self.partial_procs[id.0] + } + + pub fn insert(&mut self, symbol: Symbol, partial_proc: PartialProc<'a>) -> PartialProcId { + debug_assert!( + !self.contains_key(symbol), + "The {:?} is inserted as a partial proc twice: that's a bug!", + symbol, + ); + + let id = PartialProcId(self.symbols.len()); + + self.symbols.push(symbol); + self.partial_procs.push(partial_proc); + + id + } +} + #[derive(Clone, Debug, PartialEq)] pub struct PartialProc<'a> { pub annotation: Variable, @@ -79,7 +136,56 @@ pub struct PartialProc<'a> { pub is_self_recursive: bool, } -#[derive(Clone, Debug, PartialEq)] +impl<'a> PartialProc<'a> { + #[allow(clippy::too_many_arguments)] + pub fn from_named_function( + env: &mut Env<'a, '_>, + layout_cache: &mut LayoutCache<'a>, + annotation: Variable, + loc_args: std::vec::Vec<(Variable, Located)>, + loc_body: Located, + captured_symbols: CapturedSymbols<'a>, + is_self_recursive: bool, + ret_var: Variable, + ) -> PartialProc<'a> { + let number_of_arguments = loc_args.len(); + + match patterns_to_when(env, layout_cache, loc_args, ret_var, loc_body) { + Ok((_, pattern_symbols, body)) => { + // a named closure. Since these aren't specialized by the surrounding + // context, we can't add pending specializations for them yet. + // (If we did, all named polymorphic functions would immediately error + // on trying to convert a flex var to a Layout.) + let pattern_symbols = pattern_symbols.into_bump_slice(); + PartialProc { + annotation, + pattern_symbols, + captured_symbols, + body: body.value, + is_self_recursive, + } + } + + Err(error) => { + let mut pattern_symbols = Vec::with_capacity_in(number_of_arguments, env.arena); + + for _ in 0..number_of_arguments { + pattern_symbols.push(env.unique_symbol()); + } + + PartialProc { + annotation, + pattern_symbols: pattern_symbols.into_bump_slice(), + captured_symbols: CapturedSymbols::None, + body: roc_can::expr::Expr::RuntimeError(error.value), + is_self_recursive: false, + } + } + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] pub enum CapturedSymbols<'a> { None, Captured(&'a [(Symbol, Variable)]), @@ -139,6 +245,24 @@ impl<'a> PendingSpecialization<'a> { _lifetime: std::marker::PhantomData, } } + + /// Add a named function that will be publicly exposed to the host + pub fn from_exposed_function( + arena: &'a Bump, + subs: &Subs, + opt_annotation: Option, + fn_var: Variable, + ) -> Self { + match opt_annotation { + None => PendingSpecialization::from_var(arena, subs, fn_var), + Some(annotation) => PendingSpecialization::from_var_host_exposed( + arena, + subs, + fn_var, + &annotation.introduced_variables.host_exposed_aliases, + ), + } + } } #[derive(Clone, Debug, PartialEq)] @@ -310,7 +434,8 @@ impl<'a> Proc<'a> { #[derive(Clone, Debug)] pub struct ExternalSpecializations<'a> { - pub specs: BumpMap>, + /// Not a bumpalo vec because bumpalo is not thread safe + pub specs: BumpMap>, _lifetime: std::marker::PhantomData<&'a u8>, } @@ -326,11 +451,11 @@ impl<'a> ExternalSpecializations<'a> { use hashbrown::hash_map::Entry::{Occupied, Vacant}; let existing = match self.specs.entry(symbol) { - Vacant(entry) => entry.insert(MutSet::default()), + Vacant(entry) => entry.insert(std::vec::Vec::new()), Occupied(entry) => entry.into_mut(), }; - existing.insert(typ); + existing.push(typ); } pub fn extend(&mut self, other: Self) { @@ -338,7 +463,7 @@ impl<'a> ExternalSpecializations<'a> { for (symbol, solved_types) in other.specs { let existing = match self.specs.entry(symbol) { - Vacant(entry) => entry.insert(MutSet::default()), + Vacant(entry) => entry.insert(std::vec::Vec::new()), Occupied(entry) => entry.into_mut(), }; @@ -349,9 +474,9 @@ impl<'a> ExternalSpecializations<'a> { #[derive(Clone, Debug)] pub struct Procs<'a> { - pub partial_procs: BumpMap>, - pub imported_module_thunks: BumpSet, - pub module_thunks: BumpSet, + pub partial_procs: PartialProcs<'a>, + pub imported_module_thunks: &'a [Symbol], + pub module_thunks: &'a [Symbol], pub pending_specializations: Option, PendingSpecialization<'a>>>>, pub specialized: BumpMap<(Symbol, ProcLayout<'a>), InProgressProc<'a>>, @@ -363,9 +488,9 @@ pub struct Procs<'a> { impl<'a> Procs<'a> { pub fn new_in(arena: &'a Bump) -> Self { Self { - partial_procs: BumpMap::new_in(arena), - imported_module_thunks: BumpSet::new_in(arena), - module_thunks: BumpSet::new_in(arena), + partial_procs: PartialProcs::new_in(arena), + imported_module_thunks: &[], + module_thunks: &[], pending_specializations: Some(BumpMap::new_in(arena)), specialized: BumpMap::new_in(arena), runtime_errors: BumpMap::new_in(arena), @@ -382,14 +507,24 @@ pub enum InProgressProc<'a> { } impl<'a> Procs<'a> { + fn is_imported_module_thunk(&self, symbol: Symbol) -> bool { + self.imported_module_thunks.iter().any(|x| *x == symbol) + } + + fn is_module_thunk(&self, symbol: Symbol) -> bool { + self.module_thunks.iter().any(|x| *x == symbol) + } + + fn get_partial_proc<'b>(&'b self, symbol: Symbol) -> Option<&'b PartialProc<'a>> { + self.partial_procs.get_symbol(symbol) + } + pub fn get_specialized_procs_without_rc( self, env: &mut Env<'a, '_>, ) -> MutMap<(Symbol, ProcLayout<'a>), Proc<'a>> { let mut result = MutMap::with_capacity_and_hasher(self.specialized.len(), default_hasher()); - let cloned = self.specialized.clone(); - for (key, in_prog_proc) in self.specialized.into_iter() { match in_prog_proc { InProgress => { @@ -399,14 +534,6 @@ impl<'a> Procs<'a> { symbol, layout ); - eprintln!("other pending specializatons for this symbol:"); - - for ((bsymbol, layout), _) in cloned { - if bsymbol == symbol { - eprintln!("{:?}: {:?}", symbol, layout); - } - } - panic!(); } Done(mut proc) => { @@ -420,65 +547,9 @@ impl<'a> Procs<'a> { result } - // TODO trim down these arguments! - #[allow(clippy::too_many_arguments)] - pub fn insert_named( - &mut self, - env: &mut Env<'a, '_>, - layout_cache: &mut LayoutCache<'a>, - name: Symbol, - annotation: Variable, - loc_args: std::vec::Vec<(Variable, Located)>, - loc_body: Located, - captured_symbols: CapturedSymbols<'a>, - is_self_recursive: bool, - ret_var: Variable, - ) { - let number_of_arguments = loc_args.len(); - - match patterns_to_when(env, layout_cache, loc_args, ret_var, loc_body) { - Ok((_, pattern_symbols, body)) => { - // a named closure. Since these aren't specialized by the surrounding - // context, we can't add pending specializations for them yet. - // (If we did, all named polymorphic functions would immediately error - // on trying to convert a flex var to a Layout.) - let pattern_symbols = pattern_symbols.into_bump_slice(); - self.partial_procs.insert( - name, - PartialProc { - annotation, - pattern_symbols, - captured_symbols, - body: body.value, - is_self_recursive, - }, - ); - } - - Err(error) => { - let mut pattern_symbols = Vec::with_capacity_in(number_of_arguments, env.arena); - - for _ in 0..number_of_arguments { - pattern_symbols.push(env.unique_symbol()); - } - - self.partial_procs.insert( - name, - PartialProc { - annotation, - pattern_symbols: pattern_symbols.into_bump_slice(), - captured_symbols: CapturedSymbols::None, - body: roc_can::expr::Expr::RuntimeError(error.value), - is_self_recursive: false, - }, - ); - } - } - } - // TODO trim these down #[allow(clippy::too_many_arguments)] - pub fn insert_anonymous( + fn insert_anonymous( &mut self, env: &mut Env<'a, '_>, symbol: Symbol, @@ -504,51 +575,54 @@ impl<'a> Procs<'a> { // by the surrounding context, so we can add pending specializations // for them immediately. - let tuple = (symbol, top_level); - let already_specialized = self.specialized.contains_key(&tuple); - let (symbol, layout) = tuple; + let already_specialized = self + .specialized + .keys() + .any(|(s, t)| *s == symbol && *t == top_level); + + let layout = top_level; // if we've already specialized this one, no further work is needed. - // - // NOTE: this #[allow(clippy::map_entry)] here is for correctness! - // Changing it to use .entry() would necessarily make it incorrect. - #[allow(clippy::map_entry)] if !already_specialized { let pending = PendingSpecialization::from_var(env.arena, env.subs, annotation); - let partial_proc; - if let Some(existing) = self.partial_procs.get(&symbol) { - // if we're adding the same partial proc twice, they must be the actual same! - // - // NOTE we can't skip extra work! we still need to make the specialization for this - // invocation. The content of the `annotation` can be different, even if the variable - // number is the same - debug_assert_eq!(annotation, existing.annotation); - debug_assert_eq!(captured_symbols, existing.captured_symbols); - debug_assert_eq!(is_self_recursive, existing.is_self_recursive); - - partial_proc = existing.clone(); - } else { - let pattern_symbols = pattern_symbols.into_bump_slice(); - - partial_proc = PartialProc { - annotation, - pattern_symbols, - captured_symbols, - body: body.value, - is_self_recursive, - }; + if self.is_module_thunk(symbol) { + debug_assert!(layout.arguments.is_empty()); } match &mut self.pending_specializations { Some(pending_specializations) => { // register the pending specialization, so this gets code genned later - if self.module_thunks.contains(&symbol) { - debug_assert!(layout.arguments.is_empty()); - } add_pending(pending_specializations, symbol, layout, pending); - self.partial_procs.insert(symbol, partial_proc); + match self.partial_procs.symbol_to_id(symbol) { + Some(occupied) => { + let existing = self.partial_procs.get_id(occupied); + // if we're adding the same partial proc twice, they must be the actual same! + // + // NOTE we can't skip extra work! we still need to make the specialization for this + // invocation. The content of the `annotation` can be different, even if the variable + // number is the same + debug_assert_eq!(annotation, existing.annotation); + debug_assert_eq!(captured_symbols, existing.captured_symbols); + debug_assert_eq!(is_self_recursive, existing.is_self_recursive); + + // the partial proc is already in there, do nothing + } + None => { + let pattern_symbols = pattern_symbols.into_bump_slice(); + + let partial_proc = PartialProc { + annotation, + pattern_symbols, + captured_symbols, + body: body.value, + is_self_recursive, + }; + + self.partial_procs.insert(symbol, partial_proc); + } + } } None => { // Mark this proc as in-progress, so if we're dealing with @@ -558,8 +632,42 @@ impl<'a> Procs<'a> { let outside_layout = layout; - match specialize(env, self, symbol, layout_cache, pending, partial_proc) + let partial_proc_id = if let Some(partial_proc_id) = + self.partial_procs.symbol_to_id(symbol) { + let existing = self.partial_procs.get_id(partial_proc_id); + // if we're adding the same partial proc twice, they must be the actual same! + // + // NOTE we can't skip extra work! we still need to make the specialization for this + // invocation. The content of the `annotation` can be different, even if the variable + // number is the same + debug_assert_eq!(annotation, existing.annotation); + debug_assert_eq!(captured_symbols, existing.captured_symbols); + debug_assert_eq!(is_self_recursive, existing.is_self_recursive); + + partial_proc_id + } else { + let pattern_symbols = pattern_symbols.into_bump_slice(); + + let partial_proc = PartialProc { + annotation, + pattern_symbols, + captured_symbols, + body: body.value, + is_self_recursive, + }; + + self.partial_procs.insert(symbol, partial_proc) + }; + + match specialize( + env, + self, + symbol, + layout_cache, + pending, + partial_proc_id, + ) { Ok((proc, layout)) => { let top_level = ProcLayout::from_raw(env.arena, layout); @@ -569,7 +677,7 @@ impl<'a> Procs<'a> { proc.name ); - if self.module_thunks.contains(&proc.name) { + if self.is_module_thunk(proc.name) { debug_assert!(top_level.arguments.is_empty()); } @@ -589,50 +697,7 @@ impl<'a> Procs<'a> { } } - /// Add a named function that will be publicly exposed to the host - pub fn insert_exposed( - &mut self, - name: Symbol, - layout: ProcLayout<'a>, - arena: &'a Bump, - subs: &Subs, - opt_annotation: Option, - fn_var: Variable, - ) { - let tuple = (name, layout); - - // If we've already specialized this one, no further work is needed. - if self.specialized.contains_key(&tuple) { - return; - } - - // We're done with that tuple, so move layout back out to avoid cloning it. - let (name, layout) = tuple; - let pending = match opt_annotation { - None => PendingSpecialization::from_var(arena, subs, fn_var), - Some(annotation) => PendingSpecialization::from_var_host_exposed( - arena, - subs, - fn_var, - &annotation.introduced_variables.host_exposed_aliases, - ), - }; - - // This should only be called when pending_specializations is Some. - // Otherwise, it's being called in the wrong pass! - match &mut self.pending_specializations { - Some(pending_specializations) => { - // register the pending specialization, so this gets code genned later - add_pending(pending_specializations, name, layout, pending) - } - None => unreachable!( - r"insert_exposed was called after the pending specializations phase had already completed!" - ), - } - } - - /// TODO - pub fn insert_passed_by_name( + fn insert_passed_by_name( &mut self, env: &mut Env<'a, '_>, fn_var: Variable, @@ -651,24 +716,24 @@ impl<'a> Procs<'a> { return; } + // register the pending specialization, so this gets code genned later + if self.module_thunks.contains(&name) { + debug_assert!(layout.arguments.is_empty()); + } + // This should only be called when pending_specializations is Some. // Otherwise, it's being called in the wrong pass! match &mut self.pending_specializations { Some(pending_specializations) => { let pending = PendingSpecialization::from_var(env.arena, env.subs, fn_var); - // register the pending specialization, so this gets code genned later - if self.module_thunks.contains(&name) { - debug_assert!(layout.arguments.is_empty()); - } add_pending(pending_specializations, name, layout, pending) } None => { let symbol = name; - // TODO should pending_procs hold a Rc? - let partial_proc = match self.partial_procs.get(&symbol) { - Some(p) => p.clone(), + let partial_proc_id = match self.partial_procs.symbol_to_id(symbol) { + Some(p) => p, None => panic!("no partial_proc for {:?} in module {:?}", symbol, env.home), }; @@ -695,7 +760,7 @@ impl<'a> Procs<'a> { layout_cache, fn_var, Default::default(), - partial_proc, + partial_proc_id, ) { Ok((proc, _ignore_layout)) => { // the `layout` is a function pointer, while `_ignore_layout` can be a @@ -786,8 +851,6 @@ impl<'a, 'i> Env<'a, 'i> { pub fn unique_symbol(&mut self) -> Symbol { let ident_id = self.ident_ids.gen_unique(); - self.home.register_debug_idents(self.ident_ids); - Symbol::new(self.home, ident_id) } @@ -1118,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>, @@ -1580,7 +1647,7 @@ fn patterns_to_when<'a>( match crate::exhaustive::check( pattern.region, &[( - Located::at(pattern.region, mono_pattern.clone()), + Located::at(pattern.region, mono_pattern), crate::exhaustive::Guard::NoGuard, )], context, @@ -1703,70 +1770,74 @@ pub fn specialize_all<'a>( env: &mut Env<'a, '_>, mut procs: Procs<'a>, externals_others_need: ExternalSpecializations<'a>, + specializations_for_host: BumpMap, PendingSpecialization<'a>>>, layout_cache: &mut LayoutCache<'a>, ) -> Procs<'a> { - specialize_all_help(env, &mut procs, externals_others_need, layout_cache); + specialize_externals_others_need(env, &mut procs, externals_others_need, layout_cache); // When calling from_can, pending_specializations should be unavailable. // This must be a single pass, and we must not add any more entries to it! let opt_pending_specializations = std::mem::replace(&mut procs.pending_specializations, None); - for (name, by_layout) in opt_pending_specializations.into_iter().flatten() { + let it = specializations_for_host + .into_iter() + .chain(opt_pending_specializations.into_iter().flatten()); + + for (name, by_layout) in it { for (outside_layout, pending) in by_layout.into_iter() { // If we've already seen this (Symbol, Layout) combination before, // don't try to specialize it again. If we do, we'll loop forever! - // - // NOTE: this #[allow(clippy::map_entry)] here is for correctness! - // Changing it to use .entry() would necessarily make it incorrect. - #[allow(clippy::map_entry)] - if !procs.specialized.contains_key(&(name, outside_layout)) { - // TODO should pending_procs hold a Rc? - let partial_proc = match procs.partial_procs.get(&name) { - Some(v) => v.clone(), - None => { - // TODO this assumes the specialization is done by another module - // make sure this does not become a problem down the road! - continue; - } - }; + let key = (name, outside_layout); - // Mark this proc as in-progress, so if we're dealing with - // mutually recursive functions, we don't loop forever. - // (We had a bug around this before this system existed!) - procs.specialized.insert((name, outside_layout), InProgress); - match specialize( - env, - &mut procs, - name, - layout_cache, - pending.clone(), - partial_proc, - ) { - Ok((proc, layout)) => { - // TODO thiscode is duplicated elsewhere - let top_level = ProcLayout::from_raw(env.arena, layout); + let partial_proc = match procs.specialized.entry(key) { + Entry::Occupied(_) => { + // already specialized, just continue + continue; + } + Entry::Vacant(vacant) => { + match procs.partial_procs.symbol_to_id(name) { + Some(v) => { + // Mark this proc as in-progress, so if we're dealing with + // mutually recursive functions, we don't loop forever. + // (We had a bug around this before this system existed!) + vacant.insert(InProgress); - if procs.module_thunks.contains(&proc.name) { - debug_assert!( - top_level.arguments.is_empty(), - "{:?} from {:?}", - name, - layout - ); + v + } + None => { + // TODO this assumes the specialization is done by another module + // make sure this does not become a problem down the road! + continue; } - - debug_assert_eq!(outside_layout, top_level, " in {:?}", name); - procs.specialized.insert((name, top_level), Done(proc)); } - Err(SpecializeFailure { - attempted_layout, .. - }) => { - let proc = generate_runtime_error_function(env, name, attempted_layout); + } + }; - let top_level = ProcLayout::from_raw(env.arena, attempted_layout); + match specialize(env, &mut procs, name, layout_cache, pending, partial_proc) { + Ok((proc, layout)) => { + // TODO thiscode is duplicated elsewhere + let top_level = ProcLayout::from_raw(env.arena, layout); - procs.specialized.insert((name, top_level), Done(proc)); + if procs.is_module_thunk(proc.name) { + debug_assert!( + top_level.arguments.is_empty(), + "{:?} from {:?}", + name, + layout + ); } + + debug_assert_eq!(outside_layout, top_level, " in {:?}", name); + procs.specialized.insert((name, top_level), Done(proc)); + } + Err(SpecializeFailure { + attempted_layout, .. + }) => { + let proc = generate_runtime_error_function(env, name, attempted_layout); + + let top_level = ProcLayout::from_raw(env.arena, attempted_layout); + + procs.specialized.insert((name, top_level), Done(proc)); } } } @@ -1775,72 +1846,73 @@ pub fn specialize_all<'a>( procs } -fn specialize_all_help<'a>( +fn specialize_externals_others_need<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, externals_others_need: ExternalSpecializations<'a>, layout_cache: &mut LayoutCache<'a>, ) { - let mut symbol_solved_type = Vec::new_in(env.arena); - for (symbol, solved_types) in externals_others_need.specs.iter() { - // for some unclear reason, the MutSet does not deduplicate according to the hash - // instance. So we do it manually here - let mut as_vec: std::vec::Vec<_> = solved_types.iter().collect(); - + // de-duplicate by the Hash instance (set only deduplicates by Eq instance) use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; + let mut seen_hashes = Vec::with_capacity_in(solved_types.len(), env.arena); + let hash_the_thing = |x: &SolvedType| { let mut hasher = DefaultHasher::new(); x.hash(&mut hasher); hasher.finish() }; - as_vec.sort_by_key(|x| hash_the_thing(x)); - as_vec.dedup_by_key(|x| hash_the_thing(x)); + for solved_type in solved_types { + let hash = hash_the_thing(solved_type); - for s in as_vec { - symbol_solved_type.push((*symbol, s.clone())); - } - } - - for (name, solved_type) in symbol_solved_type.into_iter() { - let partial_proc = match procs.partial_procs.get(&name) { - Some(v) => v.clone(), - None => { - panic!("Cannot find a partial proc for {:?}", name); + if seen_hashes.iter().any(|h| *h == hash) { + // we've seen this one already + continue; } - }; - // TODO I believe this sis also duplicated - match specialize_solved_type( - env, - procs, - name, - layout_cache, - solved_type, - BumpMap::new_in(env.arena), - partial_proc, - ) { - Ok((proc, layout)) => { - let top_level = ProcLayout::from_raw(env.arena, layout); + seen_hashes.push(hash); - if procs.module_thunks.contains(&name) { - debug_assert!(top_level.arguments.is_empty()); + let name = *symbol; + + let partial_proc_id = match procs.partial_procs.symbol_to_id(name) { + Some(v) => v, + None => { + panic!("Cannot find a partial proc for {:?}", name); } + }; - procs.specialized.insert((name, top_level), Done(proc)); - } - Err(SpecializeFailure { - problem: _, - attempted_layout, - }) => { - let proc = generate_runtime_error_function(env, name, attempted_layout); + // TODO I believe this is also duplicated + match specialize_solved_type( + env, + procs, + name, + layout_cache, + solved_type, + BumpMap::new_in(env.arena), + partial_proc_id, + ) { + Ok((proc, layout)) => { + let top_level = ProcLayout::from_raw(env.arena, layout); - let top_level = ProcLayout::from_raw(env.arena, attempted_layout); + if procs.is_module_thunk(name) { + debug_assert!(top_level.arguments.is_empty()); + } - procs.specialized.insert((name, top_level), Done(proc)); + procs.specialized.insert((name, top_level), Done(proc)); + } + Err(SpecializeFailure { + problem: _, + attempted_layout, + }) => { + let proc = generate_runtime_error_function(env, name, attempted_layout); + + let top_level = ProcLayout::from_raw(env.arena, attempted_layout); + + procs.specialized.insert((name, top_level), Done(proc)); + } } } } @@ -1898,32 +1970,28 @@ fn specialize_external<'a>( layout_cache: &mut LayoutCache<'a>, fn_var: Variable, host_exposed_variables: &[(Symbol, Variable)], - partial_proc: PartialProc<'a>, + partial_proc_id: PartialProcId, ) -> Result, LayoutProblem> { - let PartialProc { - annotation, - pattern_symbols, - captured_symbols, - body, - is_self_recursive, - } = partial_proc; + let partial_proc = procs.partial_procs.get_id(partial_proc_id); + let captured_symbols = partial_proc.captured_symbols; // unify the called function with the specialized signature, then specialize the function body let snapshot = env.subs.snapshot(); let cache_snapshot = layout_cache.snapshot(); - let _unified = roc_unify::unify::unify(env.subs, annotation, fn_var); + let _unified = roc_unify::unify::unify(env.subs, partial_proc.annotation, fn_var); // This will not hold for programs with type errors // let is_valid = matches!(unified, roc_unify::unify::Unified::Success(_)); // debug_assert!(is_valid, "unificaton failure for {:?}", proc_name); // if this is a closure, add the closure record argument - let pattern_symbols = match captured_symbols { - CapturedSymbols::None => pattern_symbols, - CapturedSymbols::Captured([]) => pattern_symbols, + let pattern_symbols = match partial_proc.captured_symbols { + CapturedSymbols::None => partial_proc.pattern_symbols, + CapturedSymbols::Captured([]) => partial_proc.pattern_symbols, CapturedSymbols::Captured(_) => { - let mut temp = Vec::from_iter_in(pattern_symbols.iter().copied(), env.arena); + let mut temp = + Vec::from_iter_in(partial_proc.pattern_symbols.iter().copied(), env.arena); temp.push(Symbol::ARG_CLOSURE); temp.into_bump_slice() } @@ -2020,12 +2088,13 @@ fn specialize_external<'a>( } }; - let recursivity = if is_self_recursive { + let recursivity = if partial_proc.is_self_recursive { SelfRecursive::SelfRecursive(JoinPointId(env.unique_symbol())) } else { SelfRecursive::NotSelfRecursive }; + let body = partial_proc.body.clone(); let mut specialized_body = from_can(env, fn_var, body, procs, layout_cache); match specialized { @@ -2416,13 +2485,13 @@ struct SpecializeFailure<'a> { type SpecializeSuccess<'a> = (Proc<'a>, RawFunctionLayout<'a>); -fn specialize<'a>( +fn specialize<'a, 'b>( env: &mut Env<'a, '_>, - procs: &mut Procs<'a>, + procs: &'b mut Procs<'a>, proc_name: Symbol, layout_cache: &mut LayoutCache<'a>, pending: PendingSpecialization, - partial_proc: PartialProc<'a>, + partial_proc_id: PartialProcId, ) -> Result, SpecializeFailure<'a>> { let PendingSpecialization { solved_type, @@ -2435,9 +2504,9 @@ fn specialize<'a>( procs, proc_name, layout_cache, - solved_type, + &solved_type, host_exposed_aliases, - partial_proc, + partial_proc_id, ) } @@ -2465,18 +2534,18 @@ fn specialize_solved_type<'a>( procs: &mut Procs<'a>, proc_name: Symbol, layout_cache: &mut LayoutCache<'a>, - solved_type: SolvedType, + solved_type: &SolvedType, host_exposed_aliases: BumpMap, - partial_proc: PartialProc<'a>, + partial_proc_id: PartialProcId, ) -> Result, SpecializeFailure<'a>> { specialize_variable_help( env, procs, proc_name, layout_cache, - |env| introduce_solved_type_to_subs(env, &solved_type), + |env| introduce_solved_type_to_subs(env, solved_type), host_exposed_aliases, - partial_proc, + partial_proc_id, ) } @@ -2487,7 +2556,7 @@ fn specialize_variable<'a>( layout_cache: &mut LayoutCache<'a>, fn_var: Variable, host_exposed_aliases: BumpMap, - partial_proc: PartialProc<'a>, + partial_proc_id: PartialProcId, ) -> Result, SpecializeFailure<'a>> { specialize_variable_help( env, @@ -2496,7 +2565,7 @@ fn specialize_variable<'a>( layout_cache, |_| fn_var, host_exposed_aliases, - partial_proc, + partial_proc_id, ) } @@ -2507,7 +2576,7 @@ fn specialize_variable_help<'a, F>( layout_cache: &mut LayoutCache<'a>, fn_var_thunk: F, host_exposed_aliases: BumpMap, - partial_proc: PartialProc<'a>, + partial_proc_id: PartialProcId, ) -> Result, SpecializeFailure<'a>> where F: FnOnce(&mut Env<'a, '_>) -> Variable, @@ -2526,7 +2595,7 @@ where .raw_from_var(env.arena, fn_var, env.subs) .unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err)); - let raw = if procs.module_thunks.contains(&proc_name) { + let raw = if procs.is_module_thunk(proc_name) { match raw { RawFunctionLayout::Function(_, lambda_set, _) => { RawFunctionLayout::ZeroArgumentThunk(Layout::LambdaSet(lambda_set)) @@ -2538,7 +2607,8 @@ where }; // make sure rigid variables in the annotation are converted to flex variables - instantiate_rigids(env.subs, partial_proc.annotation); + let annotation_var = procs.partial_procs.get_id(partial_proc_id).annotation; + instantiate_rigids(env.subs, annotation_var); let mut host_exposed_variables = Vec::with_capacity_in(host_exposed_aliases.len(), env.arena); @@ -2555,7 +2625,7 @@ where layout_cache, fn_var, &host_exposed_variables, - partial_proc, + partial_proc_id, ); match specialized { @@ -2629,8 +2699,8 @@ fn specialize_naked_symbol<'a>( hole: &'a Stmt<'a>, symbol: Symbol, ) -> Stmt<'a> { - if procs.module_thunks.contains(&symbol) { - let partial_proc = procs.partial_procs.get(&symbol).unwrap(); + if procs.is_module_thunk(symbol) { + let partial_proc = procs.get_partial_proc(symbol).unwrap(); let fn_var = partial_proc.annotation; // This is a top-level declaration, which will code gen to a 0-arity thunk. @@ -2844,41 +2914,9 @@ pub fn with_hole<'a>( } } LetNonRec(def, cont, _) => { - if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value { - if let Closure { - function_type, - return_type, - recursive, - arguments, - loc_body: boxed_body, - captured_symbols, - .. - } = def.loc_expr.value - { - // Extract Procs, but discard the resulting Expr::Load. - // That Load looks up the pointer, which we won't use here! - - let loc_body = *boxed_body; - - let is_self_recursive = - !matches!(recursive, roc_can::expr::Recursive::NotRecursive); - - // this should be a top-level declaration, and hence have no captured symbols - // if we ever do hit this (and it's not a bug), we should make sure to put the - // captured symbols into a CapturedSymbols and give it to insert_named - debug_assert!(captured_symbols.is_empty()); - - procs.insert_named( - env, - layout_cache, - *symbol, - function_type, - arguments, - loc_body, - CapturedSymbols::None, - is_self_recursive, - return_type, - ); + if let roc_can::pattern::Pattern::Identifier(symbol) = def.loc_pattern.value { + if let Closure(closure_data) = def.loc_expr.value { + register_noncapturing_closure(env, procs, layout_cache, symbol, closure_data); return with_hole( env, @@ -2890,9 +2928,6 @@ pub fn with_hole<'a>( hole, ); } - } - - if let roc_can::pattern::Pattern::Identifier(symbol) = def.loc_pattern.value { // special-case the form `let x = E in x` // not doing so will drop the `hole` match &cont.value { @@ -3020,33 +3055,13 @@ pub fn with_hole<'a>( // because Roc is strict, only functions can be recursive! for def in defs.into_iter() { if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value { - if let Closure { - function_type, - return_type, - recursive, - arguments, - loc_body: boxed_body, - .. - } = def.loc_expr.value - { - // Extract Procs, but discard the resulting Expr::Load. - // That Load looks up the pointer, which we won't use here! - - let loc_body = *boxed_body; - - let is_self_recursive = - !matches!(recursive, roc_can::expr::Recursive::NotRecursive); - - procs.insert_named( + if let Closure(closure_data) = def.loc_expr.value { + register_noncapturing_closure( env, + procs, layout_cache, *symbol, - function_type, - arguments, - loc_body, - CapturedSymbols::None, - is_self_recursive, - return_type, + closure_data, ); continue; @@ -3744,7 +3759,7 @@ pub fn with_hole<'a>( } } - Closure { + Closure(ClosureData { function_type, return_type, name, @@ -3752,7 +3767,7 @@ pub fn with_hole<'a>( captured_symbols, loc_body: boxed_body, .. - } => { + }) => { let loc_body = *boxed_body; let raw = layout_cache.raw_from_var(env.arena, function_type, env.subs); @@ -3809,11 +3824,11 @@ pub fn with_hole<'a>( // if it's in there, it's a call by name, otherwise it's a call by pointer let is_known = |key| { // a proc in this module, or an imported symbol - procs.partial_procs.contains_key(key) || env.is_imported_symbol(*key) + procs.partial_procs.contains_key(key) || env.is_imported_symbol(key) }; match loc_expr.value { - roc_can::expr::Expr::Var(proc_name) if is_known(&proc_name) => { + roc_can::expr::Expr::Var(proc_name) if is_known(proc_name) => { // a call by a known name call_by_name( env, @@ -4005,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, @@ -4133,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 { @@ -4617,6 +4643,131 @@ fn sorted_field_symbols<'a>( field_symbols_temp } +/// Insert a closure that does capture symbols (because it is top-level) to the list of partial procs +fn register_noncapturing_closure<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + closure_name: Symbol, + closure_data: ClosureData, +) { + let ClosureData { + function_type, + return_type, + recursive, + arguments, + loc_body: boxed_body, + captured_symbols, + .. + } = closure_data; + + // Extract Procs, but discard the resulting Expr::Load. + // That Load looks up the pointer, which we won't use here! + + let loc_body = *boxed_body; + + let is_self_recursive = !matches!(recursive, roc_can::expr::Recursive::NotRecursive); + + // this should be a top-level declaration, and hence have no captured symbols + // if we ever do hit this (and it's not a bug), we should make sure to put the + // captured symbols into a CapturedSymbols and give it to PartialProc::from_named_function + debug_assert!(captured_symbols.is_empty()); + + let partial_proc = PartialProc::from_named_function( + env, + layout_cache, + function_type, + arguments, + loc_body, + CapturedSymbols::None, + is_self_recursive, + return_type, + ); + + procs.partial_procs.insert(closure_name, partial_proc); +} + +/// Insert a closure that may capture symbols to the list of partial procs +fn register_capturing_closure<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + closure_name: Symbol, + closure_data: ClosureData, +) { + // the function surrounding the closure definition may be specialized multiple times, + // hence in theory this partial proc may be added multiple times. That would be wasteful + // so we check whether this partial proc is already there. + // + // (the `gen_primitives::task_always_twice` test has this behavior) + if !procs.partial_procs.contains_key(closure_name) { + let ClosureData { + function_type, + return_type, + closure_type, + closure_ext_var, + recursive, + arguments, + loc_body: boxed_body, + captured_symbols, + .. + } = closure_data; + let loc_body = *boxed_body; + + let is_self_recursive = !matches!(recursive, roc_can::expr::Recursive::NotRecursive); + + // does this function capture any local values? + let function_layout = layout_cache.raw_from_var(env.arena, function_type, env.subs); + + let captured_symbols = match function_layout { + Ok(RawFunctionLayout::Function(_, lambda_set, _)) => { + if let Layout::Struct(&[]) = lambda_set.runtime_representation() { + CapturedSymbols::None + } else { + let mut temp = Vec::from_iter_in(captured_symbols, env.arena); + temp.sort(); + CapturedSymbols::Captured(temp.into_bump_slice()) + } + } + Ok(RawFunctionLayout::ZeroArgumentThunk(_)) => { + // top-level thunks cannot capture any variables + debug_assert!( + captured_symbols.is_empty(), + "{:?} with layout {:?} {:?} {:?}", + &captured_symbols, + function_layout, + env.subs, + (function_type, closure_type, closure_ext_var), + ); + CapturedSymbols::None + } + Err(_) => { + // just allow this. see https://github.com/rtfeldman/roc/issues/1585 + if captured_symbols.is_empty() { + CapturedSymbols::None + } else { + let mut temp = Vec::from_iter_in(captured_symbols, env.arena); + temp.sort(); + CapturedSymbols::Captured(temp.into_bump_slice()) + } + } + }; + + let partial_proc = PartialProc::from_named_function( + env, + layout_cache, + function_type, + arguments, + loc_body, + captured_symbols, + is_self_recursive, + return_type, + ); + + procs.partial_procs.insert(closure_name, partial_proc); + } +} + pub fn from_can<'a>( env: &mut Env<'a, '_>, variable: Variable, @@ -4736,32 +4887,13 @@ pub fn from_can<'a>( // Now that we know for sure it's a closure, get an owned // version of these variant args so we can use them properly. match def.loc_expr.value { - Closure { - function_type, - return_type, - recursive, - arguments, - loc_body: boxed_body, - .. - } => { - // Extract Procs, but discard the resulting Expr::Load. - // That Load looks up the pointer, which we won't use here! - - let loc_body = *boxed_body; - - let is_self_recursive = - !matches!(recursive, roc_can::expr::Recursive::NotRecursive); - - procs.insert_named( + Closure(closure_data) => { + register_capturing_closure( env, + procs, layout_cache, *symbol, - function_type, - arguments, - loc_body, - CapturedSymbols::None, - is_self_recursive, - return_type, + closure_data, ); continue; @@ -4776,89 +4908,12 @@ pub fn from_can<'a>( } LetNonRec(def, cont, outer_annotation) => { if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value { - if let Closure { .. } = &def.loc_expr.value { - // Now that we know for sure it's a closure, get an owned - // version of these variant args so we can use them properly. - match def.loc_expr.value { - Closure { - function_type, - return_type, - closure_type, - closure_ext_var, - recursive, - arguments, - loc_body: boxed_body, - captured_symbols, - .. - } => { - // Extract Procs, but discard the resulting Expr::Load. - // That Load looks up the pointer, which we won't use here! - - let loc_body = *boxed_body; - - let is_self_recursive = - !matches!(recursive, roc_can::expr::Recursive::NotRecursive); - - // does this function capture any local values? - let function_layout = - layout_cache.raw_from_var(env.arena, function_type, env.subs); - - let captured_symbols = match function_layout { - Ok(RawFunctionLayout::Function(_, lambda_set, _)) => { - if let Layout::Struct(&[]) = lambda_set.runtime_representation() - { - CapturedSymbols::None - } else { - let mut temp = - Vec::from_iter_in(captured_symbols, env.arena); - temp.sort(); - CapturedSymbols::Captured(temp.into_bump_slice()) - } - } - Ok(RawFunctionLayout::ZeroArgumentThunk(_)) => { - // top-level thunks cannot capture any variables - debug_assert!( - captured_symbols.is_empty(), - "{:?} with layout {:?} {:?} {:?}", - &captured_symbols, - function_layout, - env.subs, - (function_type, closure_type, closure_ext_var), - ); - CapturedSymbols::None - } - Err(_) => { - // just allow this. see https://github.com/rtfeldman/roc/issues/1585 - if captured_symbols.is_empty() { - CapturedSymbols::None - } else { - let mut temp = - Vec::from_iter_in(captured_symbols, env.arena); - temp.sort(); - CapturedSymbols::Captured(temp.into_bump_slice()) - } - } - }; - - procs.insert_named( - env, - layout_cache, - *symbol, - function_type, - arguments, - loc_body, - captured_symbols, - is_self_recursive, - return_type, - ); - - return from_can(env, variable, cont.value, procs, layout_cache); - } - _ => unreachable!(), - } - } - match def.loc_expr.value { + roc_can::expr::Expr::Closure(closure_data) => { + register_capturing_closure(env, procs, layout_cache, *symbol, closure_data); + + return from_can(env, variable, cont.value, procs, layout_cache); + } roc_can::expr::Expr::Var(original) => { // a variable is aliased, e.g. // @@ -5994,7 +6049,7 @@ fn can_reuse_symbol<'a>( if env.is_imported_symbol(symbol) { Imported(symbol) - } else if procs.partial_procs.contains_key(&symbol) { + } else if procs.partial_procs.contains_key(symbol) { LocalFunction(symbol) } else { Value(symbol) @@ -6083,7 +6138,7 @@ fn reuse_function_symbol<'a>( result: Stmt<'a>, original: Symbol, ) -> Stmt<'a> { - match procs.partial_procs.get(&original) { + match procs.get_partial_proc(original) { None => { match arg_var { Some(arg_var) if env.is_imported_symbol(original) => { @@ -6091,7 +6146,7 @@ fn reuse_function_symbol<'a>( .raw_from_var(env.arena, arg_var, env.subs) .expect("creating layout does not fail"); - if procs.imported_module_thunks.contains(&original) { + if procs.is_imported_module_thunk(original) { let layout = match raw { RawFunctionLayout::ZeroArgumentThunk(layout) => layout, RawFunctionLayout::Function(_, lambda_set, _) => { @@ -6152,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, _) => { @@ -6187,7 +6242,7 @@ fn reuse_function_symbol<'a>( closure_data, env.arena.alloc(result), ) - } else if procs.module_thunks.contains(&original) { + } else if procs.is_module_thunk(original) { // this is a 0-argument thunk // TODO suspicious @@ -6383,7 +6438,7 @@ fn call_by_name<'a>( evaluate_arguments_then_runtime_error(env, procs, layout_cache, msg, loc_args) } Ok(RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout)) => { - if procs.module_thunks.contains(&proc_name) { + if procs.is_module_thunk(proc_name) { if loc_args.is_empty() { call_by_name_module_thunk( env, @@ -6460,7 +6515,7 @@ fn call_by_name<'a>( } } Ok(RawFunctionLayout::ZeroArgumentThunk(ret_layout)) => { - if procs.module_thunks.contains(&proc_name) { + if procs.is_module_thunk(proc_name) { // here we turn a call to a module thunk into forcing of that thunk call_by_name_module_thunk( env, @@ -6533,7 +6588,8 @@ fn call_by_name_help<'a>( // If we've already specialized this one, no further work is needed. if procs .specialized - .contains_key(&(proc_name, top_level_layout)) + .keys() + .any(|x| x == &(proc_name, top_level_layout)) { debug_assert_eq!( argument_layouts.len(), @@ -6562,7 +6618,7 @@ fn call_by_name_help<'a>( add_needed_external(procs, env, original_fn_var, proc_name); debug_assert_ne!(proc_name.module_id(), ModuleId::ATTR); - if procs.imported_module_thunks.contains(&proc_name) { + if procs.is_imported_module_thunk(proc_name) { force_thunk( env, proc_name, @@ -6614,14 +6670,14 @@ fn call_by_name_help<'a>( // the same specialization independently), we work through the // queue of pending specializations to complete each specialization // exactly once. + if procs.is_module_thunk(proc_name) { + debug_assert!(top_level_layout.arguments.is_empty()); + } + match &mut procs.pending_specializations { Some(pending_specializations) => { debug_assert!(!env.is_imported_symbol(proc_name)); - if procs.module_thunks.contains(&proc_name) { - debug_assert!(top_level_layout.arguments.is_empty()); - } - // register the pending specialization, so this gets code genned later add_pending( pending_specializations, @@ -6655,7 +6711,7 @@ fn call_by_name_help<'a>( assign_to_symbols(env, procs, layout_cache, iter, result) } None => { - let opt_partial_proc = procs.partial_procs.get(&proc_name); + let opt_partial_proc = procs.partial_procs.symbol_to_id(proc_name); /* debug_assert_eq!( @@ -6672,9 +6728,6 @@ fn call_by_name_help<'a>( match opt_partial_proc { Some(partial_proc) => { - // TODO should pending_procs hold a Rc to avoid this .clone()? - let partial_proc = partial_proc.clone(); - // Mark this proc as in-progress, so if we're dealing with // mutually recursive functions, we don't loop forever. // (We had a bug around this before this system existed!) @@ -6749,17 +6802,16 @@ fn call_by_name_module_thunk<'a>( ) -> Stmt<'a> { debug_assert!(!env.is_imported_symbol(proc_name)); - // debug_assert!(!procs.module_thunks.contains(&proc_name), "{:?}", proc_name); - let top_level_layout = ProcLayout::new(env.arena, &[], *ret_layout); let inner_layout = *ret_layout; // If we've already specialized this one, no further work is needed. - if procs + let already_specialized = procs .specialized - .contains_key(&(proc_name, top_level_layout)) - { + .contains_key(&(proc_name, top_level_layout)); + + if already_specialized { force_thunk(env, proc_name, inner_layout, assigned, hole) } else { let pending = PendingSpecialization::from_var(env.arena, env.subs, fn_var); @@ -6775,14 +6827,14 @@ fn call_by_name_module_thunk<'a>( // the same specialization independently), we work through the // queue of pending specializations to complete each specialization // exactly once. + if procs.is_module_thunk(proc_name) { + debug_assert!(top_level_layout.arguments.is_empty()); + } + match &mut procs.pending_specializations { Some(pending_specializations) => { debug_assert!(!env.is_imported_symbol(proc_name)); - if procs.module_thunks.contains(&proc_name) { - debug_assert!(top_level_layout.arguments.is_empty()); - } - // register the pending specialization, so this gets code genned later add_pending( pending_specializations, @@ -6794,13 +6846,10 @@ fn call_by_name_module_thunk<'a>( force_thunk(env, proc_name, inner_layout, assigned, hole) } None => { - let opt_partial_proc = procs.partial_procs.get(&proc_name); + let opt_partial_proc = procs.partial_procs.symbol_to_id(proc_name); match opt_partial_proc { Some(partial_proc) => { - // TODO should pending_procs hold a Rc to avoid this .clone()? - let partial_proc = partial_proc.clone(); - // Mark this proc as in-progress, so if we're dealing with // mutually recursive functions, we don't loop forever. // (We had a bug around this before this system existed!) @@ -6917,7 +6966,7 @@ fn call_specialized_proc<'a>( match procs .partial_procs - .get(&proc_name) + .get_symbol(proc_name) .map(|pp| &pp.captured_symbols) { Some(&CapturedSymbols::Captured(captured_symbols)) => { @@ -7869,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>( @@ -7882,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) => { @@ -7917,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)) } @@ -7987,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()); @@ -8001,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)); @@ -8424,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()); @@ -8438,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/layout.rs b/compiler/mono/src/layout.rs index 7768852324..2c63b7658d 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -8,6 +8,7 @@ use roc_types::subs::{ Content, FlatType, RecordFields, Subs, UnionTags, Variable, VariableSubsSlice, }; use roc_types::types::{gather_fields_unsorted_iter, RecordField}; +use std::collections::hash_map::Entry; use std::collections::HashMap; use ven_pretty::{DocAllocator, DocBuilder}; @@ -723,6 +724,14 @@ impl<'a, 'b> Env<'a, 'b> { } } +const fn round_up_to_alignment(width: u32, alignment: u32) -> u32 { + if alignment != 0 && width % alignment > 0 { + width + alignment - (width % alignment) + } else { + width + } +} + impl<'a> Layout<'a> { fn new_help<'b>( env: &mut Env<'a, 'b>, @@ -858,11 +867,7 @@ impl<'a> Layout<'a> { let width = self.stack_size_without_alignment(pointer_size); let alignment = self.alignment_bytes(pointer_size); - if alignment != 0 && width % alignment > 0 { - width + alignment - (width % alignment) - } else { - width - } + round_up_to_alignment(width, alignment) } fn stack_size_without_alignment(&self, pointer_size: u32) -> u32 { @@ -884,6 +889,8 @@ impl<'a> Layout<'a> { match variant { NonRecursive(fields) => { + let tag_id_builtin = variant.tag_id_builtin(); + fields .iter() .map(|tag_layout| { @@ -893,9 +900,10 @@ impl<'a> Layout<'a> { .sum::() }) .max() + .map(|w| round_up_to_alignment(w, tag_id_builtin.alignment_bytes(pointer_size))) .unwrap_or_default() // the size of the tag_id - + variant.tag_id_builtin().stack_size(pointer_size) + + tag_id_builtin.stack_size(pointer_size) } Recursive(_) @@ -923,13 +931,28 @@ impl<'a> Layout<'a> { use UnionLayout::*; match variant { - NonRecursive(tags) => tags - .iter() - .map(|x| x.iter()) - .flatten() - .map(|x| x.alignment_bytes(pointer_size)) - .max() - .unwrap_or(0), + NonRecursive(tags) => { + let max_alignment = tags + .iter() + .flat_map(|layouts| { + layouts + .iter() + .map(|layout| layout.alignment_bytes(pointer_size)) + }) + .max(); + + match max_alignment { + Some(align) => { + let tag_id_builtin = variant.tag_id_builtin(); + + round_up_to_alignment( + align, + tag_id_builtin.alignment_bytes(pointer_size), + ) + } + None => 0, + } + } Recursive(_) | NullableWrapped { .. } | NullableUnwrapped { .. } @@ -2534,6 +2557,64 @@ struct IdsByLayout<'a> { next_id: u32, } +impl<'a> IdsByLayout<'a> { + #[inline(always)] + fn insert_layout(&mut self, layout: Layout<'a>) -> LayoutId { + match self.by_id.entry(layout) { + Entry::Vacant(vacant) => { + let answer = self.next_id; + vacant.insert(answer); + self.next_id += 1; + + LayoutId(answer) + } + Entry::Occupied(occupied) => LayoutId(*occupied.get()), + } + } + + #[inline(always)] + fn singleton_layout(layout: Layout<'a>) -> (Self, LayoutId) { + let mut by_id = HashMap::with_capacity_and_hasher(1, default_hasher()); + by_id.insert(layout, 1); + + let ids_by_layout = IdsByLayout { + by_id, + toplevels_by_id: Default::default(), + next_id: 2, + }; + + (ids_by_layout, LayoutId(1)) + } + + #[inline(always)] + fn insert_toplevel(&mut self, layout: crate::ir::ProcLayout<'a>) -> LayoutId { + match self.toplevels_by_id.entry(layout) { + Entry::Vacant(vacant) => { + let answer = self.next_id; + vacant.insert(answer); + self.next_id += 1; + + LayoutId(answer) + } + Entry::Occupied(occupied) => LayoutId(*occupied.get()), + } + } + + #[inline(always)] + fn singleton_toplevel(layout: crate::ir::ProcLayout<'a>) -> (Self, LayoutId) { + let mut toplevels_by_id = HashMap::with_capacity_and_hasher(1, default_hasher()); + toplevels_by_id.insert(layout, 1); + + let ids_by_layout = IdsByLayout { + by_id: Default::default(), + toplevels_by_id, + next_id: 2, + }; + + (ids_by_layout, LayoutId(1)) + } +} + #[derive(Default)] pub struct LayoutIds<'a> { by_symbol: MutMap>, @@ -2542,60 +2623,38 @@ pub struct LayoutIds<'a> { impl<'a> LayoutIds<'a> { /// Returns a LayoutId which is unique for the given symbol and layout. /// If given the same symbol and same layout, returns the same LayoutId. + #[inline(always)] pub fn get<'b>(&mut self, symbol: Symbol, layout: &'b Layout<'a>) -> LayoutId { - // Note: this function does some weird stuff to satisfy the borrow checker. - // There's probably a nicer way to write it that still works. - let ids = self.by_symbol.entry(symbol).or_insert_with(|| IdsByLayout { - by_id: HashMap::with_capacity_and_hasher(1, default_hasher()), - toplevels_by_id: Default::default(), - next_id: 1, - }); + match self.by_symbol.entry(symbol) { + Entry::Vacant(vacant) => { + let (ids_by_layout, layout_id) = IdsByLayout::singleton_layout(*layout); - // Get the id associated with this layout, or default to next_id. - let answer = ids.by_id.get(layout).copied().unwrap_or(ids.next_id); + vacant.insert(ids_by_layout); - // If we had to default to next_id, it must not have been found; - // store the ID we're going to return and increment next_id. - if answer == ids.next_id { - ids.by_id.insert(*layout, ids.next_id); - - ids.next_id += 1; + layout_id + } + Entry::Occupied(mut occupied_ids) => occupied_ids.get_mut().insert_layout(*layout), } - - LayoutId(answer) } /// Returns a LayoutId which is unique for the given symbol and layout. /// If given the same symbol and same layout, returns the same LayoutId. + #[inline(always)] pub fn get_toplevel<'b>( &mut self, symbol: Symbol, layout: &'b crate::ir::ProcLayout<'a>, ) -> LayoutId { - // Note: this function does some weird stuff to satisfy the borrow checker. - // There's probably a nicer way to write it that still works. - let ids = self.by_symbol.entry(symbol).or_insert_with(|| IdsByLayout { - by_id: Default::default(), - toplevels_by_id: HashMap::with_capacity_and_hasher(1, default_hasher()), - next_id: 1, - }); + match self.by_symbol.entry(symbol) { + Entry::Vacant(vacant) => { + let (ids_by_layout, layout_id) = IdsByLayout::singleton_toplevel(*layout); - // Get the id associated with this layout, or default to next_id. - let answer = ids - .toplevels_by_id - .get(layout) - .copied() - .unwrap_or(ids.next_id); + vacant.insert(ids_by_layout); - // If we had to default to next_id, it must not have been found; - // store the ID we're going to return and increment next_id. - if answer == ids.next_id { - ids.toplevels_by_id.insert(*layout, ids.next_id); - - ids.next_id += 1; + layout_id + } + Entry::Occupied(mut occupied_ids) => occupied_ids.get_mut().insert_toplevel(*layout), } - - LayoutId(answer) } } 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/mono/src/reset_reuse.rs b/compiler/mono/src/reset_reuse.rs index 490314c885..64fde943c5 100644 --- a/compiler/mono/src/reset_reuse.rs +++ b/compiler/mono/src/reset_reuse.rs @@ -58,8 +58,6 @@ impl<'a, 'i> Env<'a, 'i> { fn unique_symbol(&mut self) -> Symbol { let ident_id = self.ident_ids.gen_unique(); - self.home.register_debug_idents(self.ident_ids); - Symbol::new(self.home, ident_id) } } diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index c9bb622e47..da01308604 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -111,7 +111,7 @@ pub enum Expr<'a> { // Lookups Var { - module_name: &'a str, + module_name: &'a str, // module_name will only be filled if the original Roc code stated something like `5 + SomeModule.myVar`, module_name will be blank if it was `5 + myVar` ident: &'a str, }, 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/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index cd05ede55d..3b0c758c26 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -3733,6 +3733,30 @@ mod solve_expr { ); } + #[test] + fn str_trim() { + infer_eq_without_problem( + indoc!( + r#" + Str.trim + "# + ), + "Str -> Str", + ); + } + + #[test] + fn list_drop_last() { + infer_eq_without_problem( + indoc!( + r#" + List.dropLast + "# + ), + "List a -> List a", + ); + } + #[test] fn function_that_captures_nothing_is_not_captured() { // we should make sure that a function that doesn't capture anything it not itself captured 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 719e61da44..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) => { @@ -215,7 +215,7 @@ fn list_drop_at() { } #[test] -fn list_drop_at_mutable() { +fn list_drop_at_shared() { assert_evals_to!( indoc!( r#" @@ -235,6 +235,38 @@ fn list_drop_at_mutable() { ); } +#[test] +fn list_drop_last() { + assert_evals_to!( + "List.dropLast [1, 2, 3]", + RocList::from_slice(&[1, 2]), + RocList + ); + assert_evals_to!("List.dropLast []", RocList::from_slice(&[]), RocList); + assert_evals_to!("List.dropLast [0]", RocList::from_slice(&[]), RocList); +} + +#[test] +fn list_drop_last_mutable() { + assert_evals_to!( + indoc!( + r#" + list : List I64 + list = [ if True then 4 else 4, 5, 6 ] + + { newList: List.dropLast list, original: list } + "# + ), + ( + // new_list + RocList::from_slice(&[4, 5]), + // original + RocList::from_slice(&[4, 5, 6]), + ), + (RocList, RocList,) + ); +} + #[test] fn list_swap() { assert_evals_to!("List.swap [] 0 1", RocList::from_slice(&[]), RocList); @@ -731,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!( @@ -1917,6 +1980,57 @@ fn list_contains() { assert_evals_to!(indoc!("List.contains [] 4"), false, bool); } +#[test] +fn list_min() { + assert_evals_to!( + indoc!( + r#" + when List.min [] is + Ok val -> val + Err _ -> -1 + "# + ), + -1, + i64 + ); + assert_evals_to!( + indoc!( + r#" + when List.min [3, 1, 2] is + Ok val -> val + Err _ -> -1 + "# + ), + 1, + i64 + ); +} + +#[test] +fn list_max() { + assert_evals_to!( + indoc!( + r#" + when List.max [] is + Ok val -> val + Err _ -> -1 + "# + ), + -1, + i64 + ); + assert_evals_to!( + indoc!( + r#" + when List.max [3, 1, 2] is + Ok val -> val + Err _ -> -1 + "# + ), + 3, + i64 + ); +} #[test] fn list_sum() { diff --git a/compiler/test_gen/src/gen_str.rs b/compiler/test_gen/src/gen_str.rs index 292946403e..aebfd70c37 100644 --- a/compiler/test_gen/src/gen_str.rs +++ b/compiler/test_gen/src/gen_str.rs @@ -977,3 +977,94 @@ fn str_repeat_empty_string() { fn str_repeat_zero_times() { assert_evals_to!(indoc!(r#"Str.repeat "Roc" 0"#), RocStr::from(""), RocStr); } + +#[test] +fn str_trim_empty_string() { + assert_evals_to!(indoc!(r#"Str.trim """#), RocStr::from(""), RocStr); +} + +#[test] +fn str_trim_small_blank_string() { + assert_evals_to!(indoc!(r#"Str.trim " ""#), RocStr::from(""), RocStr); +} + +#[test] +fn str_trim_small_to_small() { + assert_evals_to!( + indoc!(r#"Str.trim " hello world ""#), + RocStr::from("hello world"), + RocStr + ); +} + +#[test] +fn str_trim_large_to_large_unique() { + assert_evals_to!( + indoc!(r#"Str.trim (Str.concat " " "hello world from a large string ")"#), + RocStr::from("hello world from a large string"), + RocStr + ); +} + +#[test] +fn str_trim_large_to_small_unique() { + assert_evals_to!( + indoc!(r#"Str.trim (Str.concat " " "hello world ")"#), + RocStr::from("hello world"), + RocStr + ); +} + +#[test] +fn str_trim_large_to_large_shared() { + assert_evals_to!( + indoc!( + r#" + original : Str + original = " hello world world " + + { trimmed: Str.trim original, original: original } + "# + ), + ( + RocStr::from(" hello world world "), + RocStr::from("hello world world"), + ), + (RocStr, RocStr) + ); +} + +#[test] +fn str_trim_large_to_small_shared() { + assert_evals_to!( + indoc!( + r#" + original : Str + original = " hello world " + + { trimmed: Str.trim original, original: original } + "# + ), + ( + RocStr::from(" hello world "), + RocStr::from("hello world"), + ), + (RocStr, RocStr) + ); +} + +#[test] +fn str_trim_small_to_small_shared() { + assert_evals_to!( + indoc!( + r#" + original : Str + original = " hello world " + + { trimmed: Str.trim original, original: original } + "# + ), + (RocStr::from(" hello world "), RocStr::from("hello world"),), + (RocStr, RocStr) + ); +} diff --git a/compiler/test_gen/src/gen_tags.rs b/compiler/test_gen/src/gen_tags.rs index adcfcf8bec..fe50a16cde 100644 --- a/compiler/test_gen/src/gen_tags.rs +++ b/compiler/test_gen/src/gen_tags.rs @@ -5,6 +5,23 @@ use crate::assert_evals_to; use indoc::indoc; use roc_std::{RocList, RocStr}; +#[test] +fn width_and_alignment_u8_u8() { + use roc_mono::layout::Builtin; + use roc_mono::layout::Layout; + use roc_mono::layout::UnionLayout; + + let t = &[Layout::Builtin(Builtin::Int8)] as &[_]; + let tt = [t, t]; + + let layout = Layout::Union(UnionLayout::NonRecursive(&tt)); + + // at the moment, the tag id uses an I64, so + let ptr_width = 8; + assert_eq!(layout.alignment_bytes(ptr_width), 8); + assert_eq!(layout.stack_size(ptr_width), 16); +} + #[test] fn applied_tag_nothing_ir() { 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..77b83cf21a --- /dev/null +++ b/compiler/test_wasm/Cargo.toml @@ -0,0 +1,28 @@ +[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" } + +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 87% rename from compiler/gen_wasm/tests/helpers/eval.rs rename to compiler/test_wasm/src/helpers/eval.rs index dd712df350..e9d509257a 100644 --- a/compiler/gen_wasm/tests/helpers/eval.rs +++ b/compiler/test_wasm/src/helpers/eval.rs @@ -2,10 +2,9 @@ use std::cell::Cell; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; +use crate::helpers::wasm32_test_result::Wasm32TestResult; use roc_can::builtins::builtin_defs_map; use roc_collections::all::{MutMap, MutSet}; -// use roc_std::{RocDec, RocList, RocOrder, RocStr}; -use crate::helpers::wasm32_test_result::Wasm32TestResult; use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory; const TEST_WRAPPER_NAME: &str = "test_wrapper"; @@ -66,18 +65,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! // { @@ -94,6 +87,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 { @@ -102,13 +102,19 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( exposed_to_host, }; - let (mut builder, main_function_index) = - roc_gen_wasm::build_module_help(&env, procedures).unwrap(); - T::insert_test_wrapper(&mut builder, TEST_WRAPPER_NAME, main_function_index); + let mut wasm_module = roc_gen_wasm::build_module_help(&env, procedures).unwrap(); - let module_bytes = builder.build().to_bytes().unwrap(); + T::insert_test_wrapper( + arena, + &mut wasm_module, + TEST_WRAPPER_NAME, + main_fn_index as u32, + ); - // for debugging (e.g. with wasm2wat) + let mut module_bytes = std::vec::Vec::with_capacity(4096); + wasm_module.serialize(&mut module_bytes); + + // for debugging (e.g. with wasm2wat or wasm-objdump) if false { use std::io::Write; @@ -138,7 +144,7 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( let store = Store::default(); // let module = Module::from_file(&store, &test_wasm_path).unwrap(); - let module = Module::from_binary(&store, &module_bytes).unwrap(); + let wasmer_module = Module::from_binary(&store, &module_bytes).unwrap(); // First, we create the `WasiEnv` use wasmer_wasi::WasiState; @@ -147,10 +153,10 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( // Then, we get the import object related to our WASI // and attach it to the Wasm instance. let import_object = wasi_env - .import_object(&module) + .import_object(&wasmer_module) .unwrap_or_else(|_| wasmer::imports!()); - Instance::new(&module, &import_object).unwrap() + Instance::new(&wasmer_module, &import_object).unwrap() } #[allow(dead_code)] @@ -188,9 +194,8 @@ where macro_rules! assert_wasm_evals_to { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { match $crate::helpers::eval::assert_wasm_evals_to_help::<$ty>($src, $expected) { - Err(msg) => println!("{:?}", msg), + Err(msg) => panic!("{:?}", msg), Ok(actual) => { - #[allow(clippy::bool_assert_comparison)] assert_eq!($transform(actual), $expected) } } 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/test_wasm/src/helpers/wasm32_test_result.rs b/compiler/test_wasm/src/helpers/wasm32_test_result.rs new file mode 100644 index 0000000000..627e4ad0a8 --- /dev/null +++ b/compiler/test_wasm/src/helpers/wasm32_test_result.rs @@ -0,0 +1,267 @@ +use bumpalo::collections::Vec; + +use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory; +use roc_gen_wasm::wasm_module::opcodes; +use roc_gen_wasm::wasm_module::{ + Align, CodeBuilder, Export, ExportType, LocalId, Signature, ValueType, WasmModule, +}; +use roc_std::{RocDec, RocList, RocOrder, RocStr}; + +pub trait Wasm32TestResult { + fn insert_test_wrapper<'a>( + arena: &'a bumpalo::Bump, + wasm_module: &mut WasmModule<'a>, + wrapper_name: &str, + main_function_index: u32, + ) { + wasm_module.add_function_signature(Signature { + param_types: Vec::with_capacity_in(0, arena), + ret_type: Some(ValueType::I32), + }); + + wasm_module.export.entries.push(Export { + name: wrapper_name.to_string(), + ty: ExportType::Func, + index: wasm_module.code.code_builders.len() as u32, + }); + + let mut code_builder = CodeBuilder::new(arena); + Self::build_wrapper_body(&mut code_builder, main_function_index); + wasm_module.code.code_builders.push(code_builder); + } + + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32); +} + +macro_rules! build_wrapper_body_primitive { + ($store_instruction: ident, $align: expr) => { + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { + let frame_pointer_id = LocalId(0); + let frame_pointer = Some(frame_pointer_id); + let local_types = &[ValueType::I32]; + let frame_size = 8; + + code_builder.get_local(frame_pointer_id); + // Raw "call" instruction. Don't bother with symbol & relocation since we're not going to link. + code_builder.inst_imm32(opcodes::CALL, 0, true, main_function_index); + code_builder.$store_instruction($align, 0); + code_builder.get_local(frame_pointer_id); + + code_builder.finalize(local_types, frame_size, frame_pointer); + } + }; +} + +macro_rules! wasm_test_result_primitive { + ($type_name: ident, $store_instruction: ident, $align: expr) => { + impl Wasm32TestResult for $type_name { + build_wrapper_body_primitive!($store_instruction, $align); + } + }; +} + +fn build_wrapper_body_stack_memory( + code_builder: &mut CodeBuilder, + main_function_index: u32, + size: usize, +) { + let local_id = LocalId(0); + let local_types = &[ValueType::I32]; + let frame_pointer = Some(local_id); + + code_builder.get_local(local_id); + // Raw "call" instruction. Don't bother with symbol & relocation since we're not going to link. + code_builder.inst_imm32(opcodes::CALL, 0, true, main_function_index); + code_builder.get_local(local_id); + code_builder.finalize(local_types, size as i32, frame_pointer); +} + +macro_rules! wasm_test_result_stack_memory { + ($type_name: ident) => { + impl Wasm32TestResult for $type_name { + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { + build_wrapper_body_stack_memory( + code_builder, + main_function_index, + $type_name::ACTUAL_WIDTH, + ) + } + } + }; +} + +wasm_test_result_primitive!(bool, i32_store8, Align::Bytes1); +wasm_test_result_primitive!(RocOrder, i32_store8, Align::Bytes1); + +wasm_test_result_primitive!(u8, i32_store8, Align::Bytes1); +wasm_test_result_primitive!(i8, i32_store8, Align::Bytes1); +wasm_test_result_primitive!(u16, i32_store16, Align::Bytes2); +wasm_test_result_primitive!(i16, i32_store16, Align::Bytes2); +wasm_test_result_primitive!(u32, i32_store, Align::Bytes4); +wasm_test_result_primitive!(i32, i32_store, Align::Bytes4); +wasm_test_result_primitive!(u64, i64_store, Align::Bytes8); +wasm_test_result_primitive!(i64, i64_store, Align::Bytes8); + +wasm_test_result_primitive!(f32, f32_store, Align::Bytes8); +wasm_test_result_primitive!(f64, f64_store, Align::Bytes8); + +wasm_test_result_stack_memory!(u128); +wasm_test_result_stack_memory!(i128); +wasm_test_result_stack_memory!(RocDec); +wasm_test_result_stack_memory!(RocStr); + +impl Wasm32TestResult for RocList { + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { + build_wrapper_body_stack_memory(code_builder, main_function_index, 12) + } +} + +impl Wasm32TestResult for &'_ T { + build_wrapper_body_primitive!(i32_store, Align::Bytes4); +} + +impl Wasm32TestResult for [T; N] +where + T: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { + build_wrapper_body_stack_memory(code_builder, main_function_index, N * T::ACTUAL_WIDTH) + } +} + +impl Wasm32TestResult for (T, U) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { + build_wrapper_body_stack_memory( + code_builder, + main_function_index, + T::ACTUAL_WIDTH + U::ACTUAL_WIDTH, + ) + } +} + +impl Wasm32TestResult for (T, U, V) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { + build_wrapper_body_stack_memory( + code_builder, + main_function_index, + T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH, + ) + } +} + +impl Wasm32TestResult for (T, U, V, W) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, + W: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { + build_wrapper_body_stack_memory( + code_builder, + main_function_index, + T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH, + ) + } +} + +impl Wasm32TestResult for (T, U, V, W, X) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, + W: Wasm32TestResult + FromWasm32Memory, + X: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { + build_wrapper_body_stack_memory( + code_builder, + main_function_index, + T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH + X::ACTUAL_WIDTH, + ) + } +} + +impl Wasm32TestResult for (T, U, V, W, X, Y) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, + W: Wasm32TestResult + FromWasm32Memory, + X: Wasm32TestResult + FromWasm32Memory, + Y: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { + build_wrapper_body_stack_memory( + code_builder, + main_function_index, + T::ACTUAL_WIDTH + + U::ACTUAL_WIDTH + + V::ACTUAL_WIDTH + + W::ACTUAL_WIDTH + + X::ACTUAL_WIDTH + + Y::ACTUAL_WIDTH, + ) + } +} + +impl Wasm32TestResult for (T, U, V, W, X, Y, Z) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, + W: Wasm32TestResult + FromWasm32Memory, + X: Wasm32TestResult + FromWasm32Memory, + Y: Wasm32TestResult + FromWasm32Memory, + Z: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { + build_wrapper_body_stack_memory( + code_builder, + main_function_index, + T::ACTUAL_WIDTH + + U::ACTUAL_WIDTH + + V::ACTUAL_WIDTH + + W::ACTUAL_WIDTH + + X::ACTUAL_WIDTH + + Y::ACTUAL_WIDTH + + Z::ACTUAL_WIDTH, + ) + } +} + +impl Wasm32TestResult for (T, U, V, W, X, Y, Z, A) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, + W: Wasm32TestResult + FromWasm32Memory, + X: Wasm32TestResult + FromWasm32Memory, + Y: Wasm32TestResult + FromWasm32Memory, + Z: Wasm32TestResult + FromWasm32Memory, + A: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { + build_wrapper_body_stack_memory( + code_builder, + main_function_index, + T::ACTUAL_WIDTH + + U::ACTUAL_WIDTH + + V::ACTUAL_WIDTH + + W::ACTUAL_WIDTH + + X::ACTUAL_WIDTH + + Y::ACTUAL_WIDTH + + Z::ACTUAL_WIDTH + + A::ACTUAL_WIDTH, + ) + } +} 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..abd2fb6d2b --- /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/types/src/solved_types.rs b/compiler/types/src/solved_types.rs index f796e127fb..a1768f6e7f 100644 --- a/compiler/types/src/solved_types.rs +++ b/compiler/types/src/solved_types.rs @@ -54,104 +54,102 @@ impl PartialEq for SolvedType { } fn hash_solved_type_help( - solved_type: &SolvedType, + initial: &SolvedType, flex_vars: &mut Vec, state: &mut H, ) { use SolvedType::*; - match solved_type { - Flex(var_id) => { - var_id_hash_help(*var_id, flex_vars, state); - } - Wildcard => "wildcard".hash(state), - EmptyRecord => "empty_record".hash(state), - EmptyTagUnion => "empty_tag_union".hash(state), - Error => "error".hash(state), - Func(arguments, closure, result) => { - for x in arguments { - hash_solved_type_help(x, flex_vars, state); - } + let mut stack = Vec::with_capacity(63); - hash_solved_type_help(closure, flex_vars, state); - hash_solved_type_help(result, flex_vars, state); - } - Apply(name, arguments) => { - name.hash(state); - for x in arguments { - hash_solved_type_help(x, flex_vars, state); - } - } - Rigid(name) => name.hash(state), - Erroneous(problem) => problem.hash(state), + stack.push(initial); - Record { fields, ext } => { - for (name, x) in fields { + while let Some(solved_type) = stack.pop() { + match solved_type { + Flex(var_id) => { + var_id_hash_help(*var_id, flex_vars, state); + } + Wildcard => "wildcard".hash(state), + EmptyRecord => "empty_record".hash(state), + EmptyTagUnion => "empty_tag_union".hash(state), + Error => "error".hash(state), + Func(arguments, closure, result) => { + stack.extend(arguments); + + stack.push(closure); + stack.push(result); + } + Apply(name, arguments) => { name.hash(state); - "record_field".hash(state); - hash_solved_type_help(x.as_inner(), flex_vars, state); + stack.extend(arguments); } - hash_solved_type_help(ext, flex_vars, state); - } + Rigid(name) => name.hash(state), + Erroneous(problem) => problem.hash(state), - TagUnion(tags, ext) => { - for (name, arguments) in tags { - name.hash(state); - for x in arguments { - hash_solved_type_help(x, flex_vars, state); + Record { fields, ext } => { + for (name, x) in fields { + name.hash(state); + "record_field".hash(state); + stack.push(x.as_inner()); } + stack.push(ext); } - hash_solved_type_help(ext, flex_vars, state); - } - FunctionOrTagUnion(_, _, ext) => { - hash_solved_type_help(ext, flex_vars, state); - } - - RecursiveTagUnion(rec, tags, ext) => { - var_id_hash_help(*rec, flex_vars, state); - for (name, arguments) in tags { - name.hash(state); - for x in arguments { - hash_solved_type_help(x, flex_vars, state); + TagUnion(tags, ext) => { + for (name, arguments) in tags { + name.hash(state); + stack.extend(arguments); } + stack.push(ext); } - hash_solved_type_help(ext, flex_vars, state); - } - Alias(name, arguments, solved_lambda_sets, actual) => { - name.hash(state); - for (name, x) in arguments { + FunctionOrTagUnion(_, _, ext) => { + stack.push(ext); + } + + RecursiveTagUnion(rec, tags, ext) => { + var_id_hash_help(*rec, flex_vars, state); + for (name, arguments) in tags { + name.hash(state); + stack.extend(arguments); + } + stack.push(ext); + } + + Alias(name, arguments, solved_lambda_sets, actual) => { name.hash(state); - hash_solved_type_help(x, flex_vars, state); + for (name, x) in arguments { + name.hash(state); + stack.push(x); + } + + for set in solved_lambda_sets { + stack.push(&set.0); + } + + stack.push(actual); } - for set in solved_lambda_sets { - hash_solved_type_help(&set.0, flex_vars, state); - } - - hash_solved_type_help(actual, flex_vars, state); - } - - HostExposedAlias { - name, - arguments, - lambda_set_variables: solved_lambda_sets, - actual, - actual_var, - } => { - name.hash(state); - for (name, x) in arguments { + HostExposedAlias { + name, + arguments, + lambda_set_variables: solved_lambda_sets, + actual, + actual_var, + } => { name.hash(state); - hash_solved_type_help(x, flex_vars, state); - } + for (name, x) in arguments { + name.hash(state); + stack.push(x); + } - for set in solved_lambda_sets { - hash_solved_type_help(&set.0, flex_vars, state); - } + for set in solved_lambda_sets { + stack.push(&set.0); + } - hash_solved_type_help(actual, flex_vars, state); - var_id_hash_help(*actual_var, flex_vars, state); + stack.push(actual); + var_id_hash_help(*actual_var, flex_vars, state); + } } } } 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 883305d1e1..727cb3d52e 100644 --- a/docs/Cargo.toml +++ b/docs/Cargo.toml @@ -14,16 +14,15 @@ 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" } roc_parse = { path = "../compiler/parse" } roc_collections = { path = "../compiler/collections" } bumpalo = { version = "3.2", features = ["collections"] } +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/docs/src/def.rs b/docs/src/def.rs index 0c562ade25..8143fb04c6 100644 --- a/docs/src/def.rs +++ b/docs/src/def.rs @@ -4,21 +4,20 @@ use roc_ast::{ lang::{self, core::def::def_to_def2::def_to_def2}, mem_pool::pool::Pool, }; -use roc_code_markup::{markup::nodes::def2_to_markup, slow_pool::SlowPool}; -use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds}; +use roc_code_markup::{markup::convert::from_def2::def2_to_markup, slow_pool::SlowPool}; +use roc_module::symbol::{IdentIds, Interns, ModuleId}; use roc_region::all::Region; use roc_types::subs::VarStore; -use crate::html::mark_node_to_html; +use crate::{docs_error::DocsResult, html::mark_node_to_html}; // html is written to buf pub fn defs_to_html<'a>( buf: &mut BumpString<'a>, defs: Vec>, env_module_id: ModuleId, - env_module_ids: &'a ModuleIds, - interns: &Interns, -) { + interns: &mut Interns, +) -> DocsResult<()> { let mut env_pool = Pool::with_capacity(1024); let env_arena = Bump::new(); @@ -34,18 +33,20 @@ pub fn defs_to_html<'a>( &mut env_pool, &mut var_store, dep_idents, - env_module_ids, + &interns.module_ids, exposed_ident_ids, ); let mut scope = lang::scope::Scope::new(env.home, env.pool, env.var_store); + scope.fill_scope(&env, &mut interns.all_ident_ids)?; + let region = Region::new(0, 0, 0, 0); for def in defs.iter() { - // TODO remove unwrap - write_def_to_bump_str_html(&def_arena, &mut env, &mut scope, region, def, interns, buf) - .unwrap(); + write_def_to_bump_str_html(&def_arena, &mut env, &mut scope, region, def, interns, buf)?; } + + Ok(()) } fn write_def_to_bump_str_html<'a, 'b>( diff --git a/docs/src/docs_error.rs b/docs/src/docs_error.rs new file mode 100644 index 0000000000..e554cd00a0 --- /dev/null +++ b/docs/src/docs_error.rs @@ -0,0 +1,45 @@ +use roc_ast::ast_error::ASTError; +use roc_module::module_err::ModuleError; +use roc_parse::parser::SyntaxError; +use snafu::{NoneError, ResultExt, Snafu}; + +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] +#[allow(clippy::enum_variant_names)] +pub enum DocsError { + WrapASTError { + #[snafu(backtrace)] + source: ASTError, + }, + WrapModuleError { + #[snafu(backtrace)] + source: ModuleError, + }, + WrapSyntaxError { + msg: String, + }, +} + +pub type DocsResult = std::result::Result; + +impl<'a> From> for DocsError { + fn from(syntax_err: SyntaxError) -> Self { + let msg = format!("{:?}", syntax_err); + + // hack to handle MarkError derive + let dummy_res: Result<(), NoneError> = Err(NoneError {}); + dummy_res.context(WrapSyntaxError { msg }).unwrap_err() + } +} + +impl From for DocsError { + fn from(ast_err: ASTError) -> Self { + Self::WrapASTError { source: ast_err } + } +} + +impl From for DocsError { + fn from(module_err: ModuleError) -> Self { + Self::WrapModuleError { source: module_err } + } +} diff --git a/docs/src/expr.rs b/docs/src/expr.rs index 5651ebf056..2aa7983631 100644 --- a/docs/src/expr.rs +++ b/docs/src/expr.rs @@ -5,7 +5,7 @@ use roc_ast::{ lang::{self, core::expr::expr_to_expr2::expr_to_expr2}, mem_pool::pool::Pool, }; -use roc_code_markup::{markup::nodes::expr2_to_markup, slow_pool::SlowPool}; +use roc_code_markup::{markup::convert::from_expr2::expr2_to_markup, slow_pool::SlowPool}; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds}; use roc_parse::ast::Expr; use roc_region::all::Region; @@ -63,6 +63,7 @@ fn write_expr_to_bump_str_html<'a, 'b>( expr2_id, &mut mark_node_pool, interns, + 0, )?; let expr2_markup_node = mark_node_pool.get(expr2_markup_id); diff --git a/docs/src/html.rs b/docs/src/html.rs index 4a85663970..47f2762117 100644 --- a/docs/src/html.rs +++ b/docs/src/html.rs @@ -7,7 +7,7 @@ pub fn mark_node_to_html<'a>( mark_node_pool: &SlowPool, buf: &mut BumpString<'a>, ) { - let additional_newlines: usize; + let mut additional_newlines = 0; match mark_node { MarkupNode::Nested { @@ -33,12 +33,13 @@ pub fn mark_node_to_html<'a>( Operator => "operator", Comma => "comma", String => "string", - FunctionName => "function_name", + FunctionName => "function-name", + FunctionArgName => "function-arg-name", Type => "type", Bracket => "bracket", Number => "number", PackageRelated => "package-related", - Variable => "variable", + Value => "value", RecordField => "recordfield", Import => "import", Provides => "provides", @@ -62,6 +63,11 @@ pub fn mark_node_to_html<'a>( additional_newlines = *newlines_at_end; } + MarkupNode::Indent { .. } => { + let content_str = mark_node.get_content(); + + write_html_to_buf(&content_str, "indent", buf); + } } for _ in 0..additional_newlines { diff --git a/docs/src/lib.rs b/docs/src/lib.rs index 1565fb6527..6021afc5ea 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -1,7 +1,8 @@ extern crate pulldown_cmark; extern crate roc_load; -use bumpalo::{collections::String as BumpString, collections::Vec as BumpVec, Bump}; +use bumpalo::{collections::String as BumpString, Bump}; use def::defs_to_html; +use docs_error::DocsResult; use expr::expr_to_html; use roc_builtins::std::StdLib; use roc_can::builtins::builtin_defs_map; @@ -19,12 +20,12 @@ use std::fs; use std::path::{Path, PathBuf}; mod def; +mod docs_error; mod expr; mod html; pub fn generate_docs_html(filenames: Vec, std_lib: StdLib, build_dir: &Path) { let loaded_modules = load_modules_for_files(filenames, std_lib); - let mut arena = Bump::new(); // // TODO: get info from a file like "elm.json" @@ -83,18 +84,6 @@ pub fn generate_docs_html(filenames: Vec, std_lib: StdLib, build_dir: & // Write each package's module docs html file for loaded_module in package.modules.iter_mut() { - arena.reset(); - - let mut exports: BumpVec<&str> = - BumpVec::with_capacity_in(loaded_module.exposed_values.len(), &arena); - - // TODO should this also include exposed_aliases? - for symbol in loaded_module.exposed_values.iter() { - exports.push(symbol.ident_str(&loaded_module.interns)); - } - - let exports = exports.into_bump_slice(); - for module_docs in loaded_module.documentation.values() { let module_dir = build_dir.join(module_docs.name.replace(".", "/").as_str()); @@ -109,7 +98,7 @@ pub fn generate_docs_html(filenames: Vec, std_lib: StdLib, build_dir: & ) .replace( "", - render_module_documentation(exports, module_docs, loaded_module).as_str(), + render_module_documentation(module_docs, loaded_module).as_str(), ); fs::write(module_dir.join("index.html"), rendered_module) @@ -148,25 +137,23 @@ pub fn syntax_highlight_top_level_defs<'a>( buf: &mut BumpString<'a>, code_str: &'a str, env_module_id: ModuleId, - env_module_ids: &'a ModuleIds, - interns: &Interns, -) -> Result> { + interns: &mut Interns, +) -> DocsResult { let trimmed_code_str = code_str.trim_end().trim(); match roc_parse::test_helpers::parse_defs_with(arena, trimmed_code_str) { Ok(vec_loc_def) => { let vec_def = vec_loc_def.iter().map(|loc| loc.value).collect(); - defs_to_html(buf, vec_def, env_module_id, env_module_ids, interns); + defs_to_html(buf, vec_def, env_module_id, interns)?; Ok(buf.to_string()) } - Err(err) => Err(err), + Err(err) => Err(err.into()), } } fn render_module_documentation( - exposed_values: &[&str], module: &ModuleDocumentation, loaded_module: &LoadedModule, ) -> String { @@ -181,6 +168,8 @@ fn render_module_documentation( .as_str(), ); + let exposed_values = loaded_module.exposed_values_str(); + for entry in &module.entries { let mut should_render_entry = true; @@ -232,7 +221,7 @@ fn render_module_documentation( if let Some(docs) = &doc_def.docs { buf.push_str( markdown_to_html( - exposed_values, + &exposed_values, &module.scope, docs.to_string(), loaded_module, @@ -243,7 +232,7 @@ fn render_module_documentation( } DocEntry::DetachedDoc(docs) => { let markdown = markdown_to_html( - exposed_values, + &exposed_values, &module.scope, docs.to_string(), loaded_module, @@ -972,6 +961,7 @@ fn markdown_to_html( let code_block_arena = Bump::new(); let mut code_block_buf = BumpString::new_in(&code_block_arena); + match syntax_highlight_expr( &code_block_arena, &mut code_block_buf, diff --git a/docs/tests/insert_syntax_highlighting.rs b/docs/tests/insert_syntax_highlighting.rs index a031fbb07b..1c33d8ba71 100644 --- a/docs/tests/insert_syntax_highlighting.rs +++ b/docs/tests/insert_syntax_highlighting.rs @@ -13,7 +13,11 @@ mod insert_doc_syntax_highlighting { use uuid::Uuid; fn expect_html(code_str: &str, want: &str, use_expr: bool) { - let loaded_module = make_mock_module(); + let mut loaded_module = if use_expr { + make_mock_module("") + } else { + make_mock_module(code_str) + }; let code_block_arena = Bump::new(); let mut code_block_buf = BumpString::new_in(&code_block_arena); @@ -40,8 +44,7 @@ mod insert_doc_syntax_highlighting { &mut code_block_buf, code_str, loaded_module.module_id, - &loaded_module.interns.module_ids, - &loaded_module.interns, + &mut loaded_module.interns, ) { Ok(highlighted_code_str) => { assert_eq!(highlighted_code_str, want); @@ -64,7 +67,7 @@ main = "Hello, world!" "#; - fn make_mock_module() -> LoadedModule { + fn make_mock_module(code_str: &str) -> LoadedModule { let temp_dir = tempdir().expect("Failed to create temporary directory for test."); let temp_file_path_buf = PathBuf::from([Uuid::new_v4().to_string(), ".roc".to_string()].join("")); @@ -74,7 +77,12 @@ main = "Hello, world!" "Failed to create temporary file for path {:?}", temp_file_full_path )); - writeln!(file, "{}", HELLO_WORLD).expect(&format!( + + let mut full_code_str = HELLO_WORLD.to_owned(); + full_code_str.push_str("\n\n"); + full_code_str.push_str(code_str); + + writeln!(file, "{}", full_code_str).expect(&format!( "Failed to write {:?} to file: {:?}", HELLO_WORLD, file )); @@ -143,16 +151,41 @@ main = "Hello, world!" #[test] fn top_level_def_value() { expect_html_def( - r#"main = "Hello, World!""#, - "main = \"Hello, World!\"\n\n", + r#"myVal = "Hello, World!""#, + "myVal = \"Hello, World!\"\n\n", + ); + } + + #[test] + fn tld_newline_in_str() { + expect_html_def( + r#"myVal = "Hello, Newline!\n""#, + "myVal = \"Hello, Newline!\n\"\n\n", ); } #[test] fn tld_list() { expect_html_def( - r#"main = [ 1, 2, 3 ]"#, - "main = [ 1, 2, 3 ]\n\n", + r#"myVal = [ 1, 2, 3 ]"#, + "myVal = [ 1, 2, 3 ]\n\n", + ); + } + + #[test] + fn call_builtin() { + expect_html_def( + r#"myVal = Str.fromInt 1234"#, + "myVal = Str.fromInt 1234\n\n", + ); + } + + #[test] + fn function() { + expect_html_def( + r#"myId = \something -> + something"#, + "myId = \\something -> \n something\n\n", ); } } 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/ed_error.rs b/editor/src/editor/ed_error.rs index 0c15916b22..90abd8db48 100644 --- a/editor/src/editor/ed_error.rs +++ b/editor/src/editor/ed_error.rs @@ -4,7 +4,8 @@ use roc_ast::ast_error::ASTError; use roc_ast::lang::core::ast::ASTNodeId; use roc_code_markup::markup_error::MarkError; use roc_code_markup::slow_pool::MarkNodeId; -use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu}; +use roc_module::module_err::ModuleError; +use snafu::{Backtrace, ErrorCompat, Snafu}; //import errors as follows: // `use crate::error::OutOfBounds;` @@ -253,7 +254,22 @@ pub enum EdError { msg: String, backtrace: Backtrace, }, - + WrapASTError { + #[snafu(backtrace)] + source: ASTError, + }, + WrapUIError { + #[snafu(backtrace)] + source: UIError, + }, + WrapMarkError { + #[snafu(backtrace)] + source: MarkError, + }, + WrapModuleError { + #[snafu(backtrace)] + source: ModuleError, + }, WrapIoError { source: std::io::Error, }, @@ -320,31 +336,25 @@ use crate::ui::ui_error::UIError; impl From for EdError { fn from(ui_err: UIError) -> Self { - let msg = format!("{}", ui_err); - - // hack to handle EdError derive - let dummy_res: Result<(), NoneError> = Err(NoneError {}); - dummy_res.context(UIErrorBacktrace { msg }).unwrap_err() + Self::WrapUIError { source: ui_err } } } impl From for EdError { fn from(mark_err: MarkError) -> Self { - let msg = format!("{}", mark_err); - - // hack to handle EdError derive - let dummy_res: Result<(), NoneError> = Err(NoneError {}); - dummy_res.context(MarkErrorBacktrace { msg }).unwrap_err() + Self::WrapMarkError { source: mark_err } } } impl From for EdError { fn from(ast_err: ASTError) -> Self { - let msg = format!("{}", ast_err); + Self::WrapASTError { source: ast_err } + } +} - // hack to handle EdError derive - let dummy_res: Result<(), NoneError> = Err(NoneError {}); - dummy_res.context(ASTErrorBacktrace { msg }).unwrap_err() +impl From for EdError { + fn from(module_err: ModuleError) -> Self { + Self::WrapModuleError { source: module_err } } } diff --git a/editor/src/editor/main.rs b/editor/src/editor/main.rs index 573a6cec83..d1b1431afa 100644 --- a/editor/src/editor/main.rs +++ b/editor/src/editor/main.rs @@ -63,7 +63,7 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box) -> Result<(), Box( code_arena: &'a Bump, caret_pos: CaretPos, // to set caret position ) -> EdResult> { - let mut module = EdModule::new(code_str, env, code_arena)?; + let mut owned_loaded_module = loaded_module; + let mut module = EdModule::new(code_str, env, &mut owned_loaded_module.interns, code_arena)?; let mut mark_node_pool = SlowPool::default(); @@ -66,7 +68,7 @@ pub fn init_model<'a>( &mut module.env, &module.ast, &mut mark_node_pool, - &loaded_module.interns, + &owned_loaded_module.interns, )?) }?; @@ -104,7 +106,7 @@ pub fn init_model<'a>( has_focus: true, caret_w_select_vec: NonEmpty::new((caret, None)), selected_block_opt: None, - loaded_module, + loaded_module: owned_loaded_module, show_debug_view: false, dirty: true, }) @@ -178,9 +180,14 @@ pub struct EdModule<'a> { //use crate::lang::ast::expr2_to_string; impl<'a> EdModule<'a> { - pub fn new(code_str: &'a str, mut env: Env<'a>, ast_arena: &'a Bump) -> EdResult> { + pub fn new( + code_str: &'a str, + mut env: Env<'a>, + interns: &mut Interns, + ast_arena: &'a Bump, + ) -> EdResult> { if !code_str.is_empty() { - let parse_res = parse_ast::parse_from_string(code_str, &mut env, ast_arena); + let parse_res = parse_ast::parse_from_string(code_str, &mut env, ast_arena, interns); match parse_res { Ok(ast) => Ok(EdModule { env, ast }), diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index 0cf5169de8..9e12f1ae3d 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -53,7 +53,6 @@ use roc_code_markup::markup::nodes::MarkupNode; use roc_code_markup::markup::nodes::EQUALS; use roc_code_markup::slow_pool::MarkNodeId; use roc_code_markup::slow_pool::SlowPool; -use roc_code_markup::syntax_highlight::HighlightStyle; use roc_collections::all::MutMap; use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; @@ -250,6 +249,7 @@ impl<'a> EdModel<'a> { mark_node_pool: &SlowPool, ) -> UIResult<()> { let mark_node = mark_node_pool.get(mark_node_id); + let node_newlines = mark_node.get_newlines_at_end(); if mark_node.is_nested() { @@ -277,9 +277,7 @@ impl<'a> EdModel<'a> { code_lines, )?; - if node_newlines == 0 { - *col_nr += node_content.len(); - } + *col_nr += node_content.len(); } if node_newlines > 0 { @@ -582,7 +580,6 @@ impl<'a> EdModel<'a> { let blank_replacement = MarkupNode::Blank { ast_node_id: sel_block.ast_node_id, attributes: Attributes::default(), - syn_high_style: HighlightStyle::Blank, parent_id_opt: expr2_level_mark_node.get_parent_id_opt(), newlines_at_end, }; diff --git a/editor/src/editor/mvc/let_update.rs b/editor/src/editor/mvc/let_update.rs index a678c0dbfb..1752be79d8 100644 --- a/editor/src/editor/mvc/let_update.rs +++ b/editor/src/editor/mvc/let_update.rs @@ -66,7 +66,7 @@ pub fn start_new_let_value(ed_model: &mut EdModel, new_char: &char) -> EdResult< let val_name_mark_node = MarkupNode::Text { content: val_name_string, ast_node_id, - syn_high_style: HighlightStyle::Variable, + syn_high_style: HighlightStyle::Value, attributes: Attributes::default(), parent_id_opt: Some(curr_mark_node_id), newlines_at_end: curr_mark_node_nls, diff --git a/editor/src/editor/mvc/tld_value_update.rs b/editor/src/editor/mvc/tld_value_update.rs index 6ca2f8e0fd..af6c3735d4 100644 --- a/editor/src/editor/mvc/tld_value_update.rs +++ b/editor/src/editor/mvc/tld_value_update.rs @@ -1,14 +1,6 @@ -use roc_ast::{ - lang::{ - core::{ - ast::ASTNodeId, - def::def2::Def2, - expr::expr2::Expr2, - pattern::{get_identifier_string, Pattern2}, - }, - env::Env, - }, - mem_pool::pool::NodeId, +use roc_ast::lang::{ + core::{ast::ASTNodeId, def::def2::Def2, expr::expr2::Expr2}, + env::Env, }; use roc_code_markup::{ markup::{ @@ -19,7 +11,7 @@ use roc_code_markup::{ slow_pool::{MarkNodeId, SlowPool}, syntax_highlight::HighlightStyle, }; -use roc_module::symbol::{Interns, Symbol}; +use roc_module::symbol::IdentId; use crate::{ editor::ed_error::{EdResult, FailedToUpdateIdentIdName, KeyNotFound}, @@ -35,20 +27,18 @@ use super::{ // Top Level Defined Value. example: `main = "Hello, World!"` pub fn tld_mark_node<'a>( - identifier_id: NodeId, + identifier_id: IdentId, expr_mark_node_id: MarkNodeId, ast_node_id: ASTNodeId, mark_node_pool: &mut SlowPool, env: &Env<'a>, - interns: &Interns, ) -> EdResult { - let pattern2 = env.pool.get(identifier_id); - let val_name = get_identifier_string(pattern2, interns)?; + let val_name = env.ident_ids.get_name_str_res(identifier_id)?; let val_name_mn = MarkupNode::Text { - content: val_name, + content: val_name.to_owned(), ast_node_id, - syn_high_style: HighlightStyle::Variable, + syn_high_style: HighlightStyle::Value, attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, @@ -107,22 +97,16 @@ pub fn start_new_tld_value(ed_model: &mut EdModel, new_char: &char) -> EdResult< .fail()? } - let val_symbol = Symbol::new(ed_model.module.env.home, ident_id); - - let patt2 = Pattern2::Identifier(val_symbol); - let patt2_id = ed_model.module.env.pool.add(patt2); - let tld_mark_node = tld_mark_node( - patt2_id, + ident_id, val_expr_mn_id, ast_node_id, &mut ed_model.mark_node_pool, &ed_model.module.env, - &ed_model.loaded_module.interns, )?; let new_ast_node = Def2::ValueDef { - identifier_id: patt2_id, + identifier_id: ident_id, expr_id: val_expr_id, }; diff --git a/editor/src/editor/render_ast.rs b/editor/src/editor/render_ast.rs index 00d035a692..8dcda658e7 100644 --- a/editor/src/editor/render_ast.rs +++ b/editor/src/editor/render_ast.rs @@ -5,6 +5,7 @@ use crate::graphics::primitives::text as gr_text; use cgmath::Vector2; use roc_code_markup::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER}; use roc_code_markup::slow_pool::{MarkNodeId, SlowPool}; +use roc_code_markup::syntax_highlight::HighlightStyle; use winit::dpi::PhysicalSize; use crate::{editor::config::Config, graphics::colors}; @@ -129,7 +130,7 @@ fn markup_to_wgpu_helper<'a>( } => { let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, syn_high_style)?; - let full_content = markup_node.get_full_content(); + let full_content = markup_node.get_full_content().replace("\n", "\\n"); // any \n left here should be escaped so that it can be shown as \n let glyph_text = glyph_brush::OwnedText::new(full_content) .with_color(colors::to_slice(*highlight_color)) @@ -147,7 +148,6 @@ fn markup_to_wgpu_helper<'a>( MarkupNode::Blank { ast_node_id: _, attributes: _, - syn_high_style, parent_id_opt: _, newlines_at_end, } => { @@ -157,7 +157,8 @@ fn markup_to_wgpu_helper<'a>( .with_color(colors::to_slice(colors::WHITE)) .with_scale(code_style.font_size); - let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, syn_high_style)?; + let highlight_color = + map_get(&code_style.ed_theme.syntax_high_map, &HighlightStyle::Blank)?; let char_width = code_style.glyph_dim_rect.width; let char_height = code_style.glyph_dim_rect.height; @@ -184,6 +185,17 @@ fn markup_to_wgpu_helper<'a>( txt_row_col.1 = 0; } } + MarkupNode::Indent { .. } => { + let full_content: String = markup_node.get_content(); + + txt_row_col.1 += full_content.len(); + + let glyph_text = glyph_brush::OwnedText::new(full_content) + .with_color(colors::to_slice(colors::WHITE)) + .with_scale(code_style.font_size); + + wgpu_texts.push(glyph_text); + } }; Ok(()) diff --git a/examples/cli/platform/Cargo.lock b/examples/cli/platform/Cargo.lock index cfd1e1e09d..f61398f7b6 100644 --- a/examples/cli/platform/Cargo.lock +++ b/examples/cli/platform/Cargo.lock @@ -12,9 +12,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5" +checksum = "a60553f9a9e039a333b4e9b20573b9e9b9c0bb3a11e201ccc48ef4283456d673" [[package]] name = "roc_std" diff --git a/examples/false-interpreter/False.roc b/examples/false-interpreter/False.roc index bbede4fa3a..7f83d5cfda 100644 --- a/examples/false-interpreter/False.roc +++ b/examples/false-interpreter/False.roc @@ -11,7 +11,7 @@ app "false" # 1) The input files are considered too large to just read in at once. Instead it is read via buffer or line. # 2) The output is also considered too large to generate in memory. It must be printed as we go via buffer or line. -# I think one of the biggest issues with this implementation is that it doesn't return the the platform frequently enough. +# I think one of the biggest issues with this implementation is that it doesn't return to the platform frequently enough. # What I mean by that is we build a chain of all Tasks period and return that to the host. # In something like the elm architecture you return a single step with one Task. # The huge difference here is when it comes to things like stack overflows. diff --git a/examples/false-interpreter/platform/src/lib.rs b/examples/false-interpreter/platform/src/lib.rs index 1fcc194994..4f6f8f0092 100644 --- a/examples/false-interpreter/platform/src/lib.rs +++ b/examples/false-interpreter/platform/src/lib.rs @@ -30,12 +30,12 @@ extern "C" { } #[no_mangle] -pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *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/hello-world b/examples/hello-rust/hello-world new file mode 100755 index 0000000000..04439a158d Binary files /dev/null and b/examples/hello-rust/hello-world differ 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/examples/hello-swift/.gitignore b/examples/hello-swift/.gitignore new file mode 100644 index 0000000000..a0e866b7eb --- /dev/null +++ b/examples/hello-swift/.gitignore @@ -0,0 +1 @@ +hello-swift \ No newline at end of file diff --git a/examples/hello-swift/Hello.roc b/examples/hello-swift/Hello.roc new file mode 100644 index 0000000000..d12af0371a --- /dev/null +++ b/examples/hello-swift/Hello.roc @@ -0,0 +1,10 @@ +app "hello-swift" + packages { base: "platform" } + imports [] + provides [ main ] to base + +main = + host = "Swift" + app = "Roc" + + "Hello \(host), meet \(app)" diff --git a/examples/hello-swift/platform/Package-Config.roc b/examples/hello-swift/platform/Package-Config.roc new file mode 100644 index 0000000000..51904be929 --- /dev/null +++ b/examples/hello-swift/platform/Package-Config.roc @@ -0,0 +1,10 @@ +platform examples/hello-swift + requires {}{ main : Str } + exposes [] + packages {} + imports [] + provides [ mainForHost ] + effects fx.Effect {} + +mainForHost : Str +mainForHost = main diff --git a/examples/hello-swift/platform/host.h b/examples/hello-swift/platform/host.h new file mode 100644 index 0000000000..12a134505f --- /dev/null +++ b/examples/hello-swift/platform/host.h @@ -0,0 +1,8 @@ +#include + +struct RocStr { + char* bytes; + size_t len; +}; + +extern struct RocStr roc__mainForHost_1_exposed(); diff --git a/examples/hello-swift/platform/host.swift b/examples/hello-swift/platform/host.swift new file mode 100644 index 0000000000..747f62b6ee --- /dev/null +++ b/examples/hello-swift/platform/host.swift @@ -0,0 +1,58 @@ +import Foundation + +@_cdecl("roc_alloc") +func rocAlloc(size: Int, _alignment: UInt) -> UInt { + guard let ptr = malloc(size) else { + return 0 + } + return UInt(bitPattern: ptr) +} + +@_cdecl("roc_dealloc") +func rocDealloc(ptr: UInt, _alignment: UInt) { + free(UnsafeMutableRawPointer(bitPattern: ptr)) +} + +@_cdecl("roc_realloc") +func rocRealloc(ptr: UInt, _oldSize: Int, newSize: Int, _alignment: UInt) -> UInt { + guard let ptr = realloc(UnsafeMutableRawPointer(bitPattern: ptr), newSize) else { + return 0 + } + return UInt(bitPattern: ptr) +} + +extension RocStr { + var isSmallString: Bool { + len < 0 + } + + var length: Int { + if isSmallString { + var len = len + let count = MemoryLayout.size(ofValue: len) + let bytes = Data(bytes: &len, count: count) + let lastByte = bytes[count - 1] + return Int(lastByte ^ 0b1000_0000) + } else { + return len + } + } + + var string: String { + if isSmallString { + let data: Data = withUnsafePointer(to: self) { ptr in + Data(bytes: ptr, count: length) + } + return String(data: data, encoding: .utf8)! + } else { + let data = Data(bytes: bytes, count: len) + return String(data: data, encoding: .utf8)! + } + } +} + +@_cdecl("main") +func main() -> UInt8 { + print(roc__mainForHost_1_exposed().string) + return 0 +} diff --git a/linker/Cargo.toml b/linker/Cargo.toml index 843bf6caac..27f23a217f 100644 --- a/linker/Cargo.toml +++ b/linker/Cargo.toml @@ -22,8 +22,7 @@ roc_mono = { path = "../compiler/mono" } roc_build = { path = "../compiler/build", default-features = false } roc_collections = { path = "../compiler/collections" } bumpalo = { version = "3.6", features = ["collections"] } -# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it -clap = { git = "https://github.com/rtfeldman/clap", branch = "master" } +clap = "= 3.0.0-beta.1" iced-x86 = "1.14" memmap2 = "0.3" object = { version = "0.26", features = ["read", "write"] } diff --git a/linker/src/lib.rs b/linker/src/lib.rs index be4aa891c9..6937626d31 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -59,36 +59,36 @@ pub fn build_app<'a>() -> App<'a> { .about("Preprocesses a dynamically linked platform to prepare for linking.") .arg( Arg::with_name(EXEC) - .help("The dynamically linked platform executable") + .about("The dynamically linked platform executable") .required(true), ) .arg( Arg::with_name(METADATA) - .help("Where to save the metadata from preprocessing") + .about("Where to save the metadata from preprocessing") .required(true), ) .arg( Arg::with_name(OUT) - .help("The modified version of the dynamically linked platform executable") + .about("The modified version of the dynamically linked platform executable") .required(true), ) .arg( Arg::with_name(SHARED_LIB) - .help("The name of the shared library used in building the platform") + .about("The name of the shared library used in building the platform") .default_value("libapp.so"), ) .arg( Arg::with_name(FLAG_VERBOSE) .long(FLAG_VERBOSE) .short('v') - .help("Enable verbose printing") + .about("Enable verbose printing") .required(false), ) .arg( Arg::with_name(FLAG_TIME) .long(FLAG_TIME) .short('t') - .help("Print timing information") + .about("Print timing information") .required(false), ), ) @@ -97,17 +97,17 @@ pub fn build_app<'a>() -> App<'a> { .about("Links a preprocessed platform with a Roc application.") .arg( Arg::with_name(APP) - .help("The Roc application object file waiting to be linked") + .about("The Roc application object file waiting to be linked") .required(true), ) .arg( Arg::with_name(METADATA) - .help("The metadata created by preprocessing the platform") + .about("The metadata created by preprocessing the platform") .required(true), ) .arg( Arg::with_name(OUT) - .help( + .about( "The modified version of the dynamically linked platform. \ It will be consumed to make linking faster.", ) @@ -117,14 +117,14 @@ pub fn build_app<'a>() -> App<'a> { Arg::with_name(FLAG_VERBOSE) .long(FLAG_VERBOSE) .short('v') - .help("Enable verbose printing") + .about("Enable verbose printing") .required(false), ) .arg( Arg::with_name(FLAG_TIME) .long(FLAG_TIME) .short('t') - .help("Print timing information") + .about("Print timing information") .required(false), ), ) diff --git a/nix/sources.json b/nix/sources.json index 7f02e9546f..1d90464d5d 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -17,10 +17,10 @@ "homepage": "", "owner": "NixOS", "repo": "nixpkgs", - "rev": "1441fa74d213d7cc120d9d7d49e540c1fc59bc58", - "sha256": "152qb7ch0r4bidik33zd0a9wl0929zr0dqs5l5ksm7vh3assc7sc", + "rev": "51acb65b302551ac7993b437cc6863fe9fa8ae50", + "sha256": "0si8s2ji4prp614q3050x4sp282wxgp0mm5q50slcf5f75jw5yhh", "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/1441fa74d213d7cc120d9d7d49e540c1fc59bc58.tar.gz", + "url": "https://github.com/NixOS/nixpkgs/archive/51acb65b302551ac7993b437cc6863fe9fa8ae50.tar.gz", "url_template": "https://github.com///archive/.tar.gz" } } diff --git a/roc-for-elm-programmers.md b/roc-for-elm-programmers.md index c624fd8985..726bda0114 100644 --- a/roc-for-elm-programmers.md +++ b/roc-for-elm-programmers.md @@ -509,7 +509,7 @@ Elm's custom types) instead of product types (such as records). > algebraic data types, and they have the usual support for pattern matching, > exhaustiveness checking, and so on. -You don't need to declare tag unions them before using them. Instead, you can +You don't need to declare tag unions before using them. Instead, you can just write a *tag* (essentially a variant) anywhere you like, and Roc will infer the type of the union it goes in. @@ -847,7 +847,7 @@ error. However, the `/` operator in Roc is infix syntax sugar for `Num.div`, which is a normal function you can pass to anything you like. Elm has one unary operator, namely `-`. (In Elm, `-x` means -"apply unary `negate` to `x`.") Roc has that one, and also unary `!`. +"apply unary `negate` to `x`.") Roc has that one too, and also unary `!`. The expression `!foo` desugars to `Bool.not foo`, and `!foo bar` desugars to `Bool.not (foo bar)`. 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 cfbf59f3fe..3df6678197 100644 --- a/shell.nix +++ b/shell.nix @@ -17,7 +17,6 @@ let linuxInputs = with pkgs; lib.optionals stdenv.isLinux [ - glibc_multi valgrind vulkan-headers vulkan-loader @@ -28,6 +27,7 @@ let xorg.libXrandr xorg.libXi xorg.libxcb + alsa-lib ]; llvmPkgs = pkgs.llvmPackages_12; @@ -71,9 +71,39 @@ in pkgs.mkShell { # Additional Env vars LLVM_SYS_120_PREFIX = "${llvmPkgs.llvm.dev}"; - NIXOS_GLIBC_PATH = + 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); + + 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. + ""; + + } diff --git a/vendor/morphic_lib/src/analyze.rs b/vendor/morphic_lib/src/analyze.rs index fbb69785ef..128441f884 100644 --- a/vendor/morphic_lib/src/analyze.rs +++ b/vendor/morphic_lib/src/analyze.rs @@ -1768,3 +1768,56 @@ pub(crate) fn analyze(tc: TypeCache, program: &ir::Program) -> ProgramSolutions entry_points: entry_point_solutions, } } + +// Utilities for producing "trivial" solutions, in which each function has exactly one +// specialization and all update modes are `Immutable`: + +fn hash_func_id_trivial(func_id: FuncId) -> api::FuncSpec { + use sha2::{Digest, Sha256}; + let mut hasher = Sha256::new(); + hasher.update(&func_id.0.to_le_bytes()); + api::FuncSpec(hasher.finalize().into()) +} + +fn func_solution_trivial(func_def: &ir::FuncDef) -> FuncSolution { + let update_modes = IdVec::filled_with(func_def.graph.update_mode_vars(), || { + api::UpdateMode::Immutable + }); + let mut callee_specs = IdVec::filled_with(func_def.graph.callee_spec_vars(), || None); + for val_id in func_def.graph.values().count().iter() { + if let ir::ValueKind::Op(ir::OpKind::Call { + callee_spec_var, + callee, + }) = &func_def.graph.values().node(val_id).op.kind + { + replace_none( + &mut callee_specs[callee_spec_var], + hash_func_id_trivial(*callee), + ) + .unwrap(); + } + } + FuncSolution { + update_modes, + callee_specs: callee_specs.into_mapped(|_, spec| spec.unwrap()), + } +} + +pub(crate) fn analyze_trivial(program: &ir::Program) -> ProgramSolutions { + let funcs = FuncSolutions { + solutions: program.funcs.map(|func_id, func_def| { + std::iter::once(( + hash_func_id_trivial(func_id), + Some(func_solution_trivial(func_def)), + )) + .collect() + }), + }; + let entry_points = program + .entry_points + .map(|_, &func_id| hash_func_id_trivial(func_id)); + ProgramSolutions { + funcs, + entry_points, + } +} diff --git a/vendor/morphic_lib/src/api.rs b/vendor/morphic_lib/src/api.rs index 893e2a1c48..e9d7b6aed4 100644 --- a/vendor/morphic_lib/src/api.rs +++ b/vendor/morphic_lib/src/api.rs @@ -3,15 +3,16 @@ use smallvec::SmallVec; use std::collections::{btree_map::Entry, BTreeMap}; use std::rc::Rc; -use crate::analyze; use crate::preprocess; use crate::render_api_ir; +use crate::type_cache::TypeCache; use crate::util::blocks::Blocks; use crate::util::id_bi_map::IdBiMap; use crate::util::id_type::Count; use crate::util::id_vec::IdVec; use crate::util::op_graph::OpGraph; use crate::util::replace_none::replace_none; +use crate::{analyze, ir}; #[derive(Clone, thiserror::Error, Debug)] #[non_exhaustive] @@ -1533,6 +1534,8 @@ impl Solutions { } } +// TODO: Remove this; it's only used in obsolete logic for populating const definitions with trivial +// solutions fn populate_specs( callee_spec_var_ids: Count, vals: &OpGraph, @@ -1555,11 +1558,14 @@ fn populate_specs( results.into_mapped(|_, spec| spec.unwrap()) } -pub fn solve(api_program: Program) -> Result { +fn solve_with( + api_program: Program, + analysis: impl for<'a> FnOnce(TypeCache, &'a ir::Program) -> analyze::ProgramSolutions, +) -> Result { let (nc, tc, program) = preprocess::preprocess(&api_program).map_err(ErrorKind::PreprocessError)?; - let mut solutions = analyze::analyze(tc, &program); + let mut solutions = analysis(tc, &program); Ok(Solutions { mods: api_program @@ -1600,6 +1606,8 @@ pub fn solve(api_program: Program) -> Result { .const_defs .into_iter() .map(|(const_name, const_def)| { + // TODO: This is left over from the original stub implementation, and + // generates incorrect callee specialization hashes! let callee_specs = populate_specs( const_def.builder.expr_builder.callee_spec_vars.count(), &const_def.builder.expr_builder.vals, @@ -1629,12 +1637,29 @@ pub fn solve(api_program: Program) -> Result { }) } +/// Solve for optimized update modes and function specializations +pub fn solve(api_program: Program) -> Result { + solve_with(api_program, analyze::analyze) +} + +/// Return a "trivial" solution for the program, setting every update mode to `Immutable`. +/// +/// This function does not perform any expensive analysis, but it does typecheck the input program, +/// so it can be useful for verifying the correctness of a generated program. +pub fn solve_trivial(api_program: Program) -> Result { + solve_with(api_program, |_, program| analyze::analyze_trivial(program)) +} + +// TODO: Remove this; it's only used in obsolete logic for populating const definitions with trivial +// solutions fn hash_bstr(hasher: &mut Sha256, bstr: &[u8]) { let header = (bstr.len() as u64).to_le_bytes(); hasher.update(&header); hasher.update(bstr); } +// TODO: Remove this; it's only used in obsolete logic for populating const definitions with trivial +// solutions fn hash_func_name(mod_: ModName, func: FuncName) -> FuncSpec { let mut hasher = Sha256::new(); hash_bstr(&mut hasher, mod_.0); diff --git a/version.txt b/version.txt new file mode 100644 index 0000000000..27ffb11fc3 --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ + \ No newline at end of file