diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 1279035120..512d4113fc 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -1,4 +1,7 @@ -on: [pull_request] +on: + pull_request: + paths-ignore: + - '**.md' name: Benchmarks @@ -7,7 +10,7 @@ env: jobs: prep-dependency-container: - name: cd cli; cargo criterion + name: benchmark roc programs runs-on: [self-hosted, i7-6700K] timeout-minutes: 60 env: @@ -31,5 +34,12 @@ jobs: - name: on current branch; prepare a self-contained benchmark folder run: ./ci/safe-earthly.sh +prep-bench-folder - - name: execute benchmarks with regression check - run: ./ci/bench-runner.sh + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + + - name: build benchmark runner + run: cd ci/bench-runner && cargo build --release && cd ../.. + + - name: run benchmarks with regression check + run: ./ci/bench-runner/target/release/bench-runner --check-executables-changed diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5fd1f8f542..f3e6f6051a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,7 @@ -on: [pull_request] +on: + pull_request: + paths-ignore: + - '**.md' name: CI diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml new file mode 100644 index 0000000000..fd319f4e65 --- /dev/null +++ b/.github/workflows/spellcheck.yml @@ -0,0 +1,24 @@ +on: [pull_request] + +name: SpellCheck + +env: + RUST_BACKTRACE: 1 + +jobs: + spell-check: + name: spell check + runs-on: [self-hosted] + timeout-minutes: 10 + env: + FORCE_COLOR: 1 + steps: + - uses: actions/checkout@v2 + with: + clean: "true" + + - name: Earthly version + run: earthly --version + + - name: install spell checker, do spell check + run: ./ci/safe-earthly.sh +check-typos diff --git a/.gitignore b/.gitignore index cffca3739b..95abaa59e3 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,5 @@ sccache_dir # self-contained benchmark folder bench-folder* - +# earthly +earthly_log.txt diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index c40a02e40d..c4ec83a944 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -1,12 +1,10 @@ # Building the Roc compiler from source -## Installing LLVM, Python, Zig, valgrind, libunwind, and libc++-dev +## Installing LLVM, Zig, valgrind, and Python 2.7 To build the compiler, you need these installed: -* `libunwind` (macOS should already have this one installed) -* `libc++-dev` * Python 2.7 (Windows only), `python-is-python3` (Ubuntu) * [Zig](https://ziglang.org/), see below for version * LLVM, see below for version @@ -18,11 +16,6 @@ Alternatively, you can use `cargo test --no-fail-fast` or `cargo test -p specifi For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal developtment you should be fine without it. -### libunwind & libc++-dev - -MacOS systems should already have `libunwind`, but other systems will need to install it (On Ubuntu, this can be done with `sudo apt-get install libunwind-dev`). -Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev`.) - ### libcxb libraries You may see an error like this during builds: @@ -151,6 +144,8 @@ That will help us improve this document for everyone who reads it in the future! ### LLVM installation on Linux +For a current list of all dependency versions and their names in apt, see the Earthfile. + On some Linux systems we've seen the error "failed to run custom build command for x11". On Ubuntu, running `sudo apt install pkg-config cmake libx11-dev` fixed this. @@ -205,8 +200,8 @@ Create `~/.cargo/config.toml` if it does not exist and add this to it: rustflags = ["-C", "link-arg=-fuse-ld=lld", "-C", "target-cpu=native"] ``` -Then install `lld` version 9 (e.g. with `$ sudo apt-get install lld-9`) +Then install `lld` version 12 (e.g. with `$ sudo apt-get install lld-12`) and add make sure there's a `ld.lld` executable on your `PATH` which -is symlinked to `lld-9`. +is symlinked to `lld-12`. That's it! Enjoy the faster builds. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d1ec644579..1b4b0a3214 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,9 +10,8 @@ Check [Build from source](BUILDING_FROM_SOURCE.md) for instructions. ## Running Tests -To run all tests as they are run on CI, [install earthly](https://earthly.dev/get-earthly) and run: +To run all tests and checks as they are run on CI, [install earthly](https://earthly.dev/get-earthly) and run: ``` -mkdir -p sccache_dir earthly +test-all ``` @@ -20,8 +19,9 @@ Earthly may temporarily use a lot of disk space, up to 90 GB. This disk space is ## Contribution Tips -- Before making your first pull request, definitely talk to an existing contributor on [Roc Zulip](https://roc.zulipchat.com/join/rz7n4d42v7tfilp3njzbm5eg/) first about what you plan to do! This can not only avoid duplicated effort, it can also avoid making a whole PR only to discover it won't be accepted because the change doesn't fit with the goals of the language's design or implementation. +- Before making your first pull request, definitely talk to an existing contributor on [Roc Zulip](https://roc.zulipchat.com) first about what you plan to do! This can not only avoid duplicated effort, it can also avoid making a whole PR only to discover it won't be accepted because the change doesn't fit with the goals of the language's design or implementation. - It's a good idea to open a work-in-progress pull request as you begin working on something. This way, others can see that you're working on it, which avoids duplicate effort, and others can give feedback sooner rather than later if they notice a problem in the direction things are going. Be sure to include "WIP" in the title of the PR as long as it's not ready for review! +- Make sure to create a branch on the roc repository for your changes. We do not allow CI to be run on forks for security. ## Can we do better? diff --git a/Cargo.lock b/Cargo.lock index d9d517ffab..22c465dd30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -402,6 +402,7 @@ dependencies = [ "bumpalo", "criterion", "inlinable_string", + "rlimit", "roc_cli", "roc_collections", "roc_load", @@ -2979,6 +2980,15 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" +[[package]] +name = "rlimit" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc0bf25554376fd362f54332b8410a625c71f15445bca32ffdfdf4ec9ac91726" +dependencies = [ + "libc", +] + [[package]] name = "roc_build" version = "0.1.0" @@ -3037,7 +3047,6 @@ dependencies = [ "im 14.3.0", "im-rc 14.3.0", "indoc 0.3.6", - "inlinable_string", "maplit", "pretty_assertions 0.5.1", "quickcheck 0.8.5", @@ -3065,7 +3074,6 @@ dependencies = [ "im-rc 14.3.0", "indoc 0.3.6", "inkwell 0.1.0", - "inlinable_string", "libc", "libloading 0.6.7", "maplit", @@ -3140,6 +3148,7 @@ dependencies = [ "roc_collections", "roc_load", "roc_module", + "roc_parse", "roc_region", "roc_types", ] @@ -3161,7 +3170,6 @@ dependencies = [ "im 15.0.0", "im-rc 15.0.0", "indoc 1.0.3", - "inlinable_string", "libc", "log", "maplit", @@ -3207,7 +3215,6 @@ dependencies = [ "im 14.3.0", "im-rc 14.3.0", "indoc 0.3.6", - "inlinable_string", "maplit", "pretty_assertions 0.5.1", "quickcheck 0.8.5", @@ -3226,7 +3233,6 @@ dependencies = [ "im 14.3.0", "im-rc 14.3.0", "indoc 0.3.6", - "inlinable_string", "itertools 0.9.0", "libc", "libloading 0.6.7", @@ -3265,7 +3271,6 @@ dependencies = [ "im-rc 14.3.0", "indoc 0.3.6", "inkwell 0.1.0", - "inlinable_string", "libc", "maplit", "morphic_lib", @@ -3291,6 +3296,10 @@ dependencies = [ "tokio", ] +[[package]] +name = "roc_ident" +version = "0.1.0" + [[package]] name = "roc_load" version = "0.1.0" @@ -3298,7 +3307,6 @@ dependencies = [ "bumpalo", "crossbeam", "indoc 0.3.6", - "inlinable_string", "maplit", "morphic_lib", "num_cpus", @@ -3329,12 +3337,13 @@ version = "0.1.0" dependencies = [ "bumpalo", "indoc 0.3.6", - "inlinable_string", "lazy_static", "maplit", "pretty_assertions 0.5.1", "roc_collections", + "roc_ident", "roc_region", + "static_assertions", ] [[package]] @@ -3362,6 +3371,7 @@ dependencies = [ "roc_types", "roc_unify", "ven_ena", + "ven_graph", "ven_pretty", ] @@ -3372,7 +3382,6 @@ dependencies = [ "bumpalo", "encode_unicode", "indoc 0.3.6", - "inlinable_string", "pretty_assertions 0.5.1", "quickcheck 0.8.5", "quickcheck_macros 0.8.0", @@ -3386,7 +3395,6 @@ name = "roc_problem" version = "0.1.0" dependencies = [ "indoc 0.3.6", - "inlinable_string", "maplit", "pretty_assertions 0.5.1", "quickcheck 0.8.5", @@ -3410,7 +3418,6 @@ dependencies = [ "im 14.3.0", "im-rc 14.3.0", "indoc 0.3.6", - "inlinable_string", "maplit", "pretty_assertions 0.5.1", "quickcheck 0.8.5", @@ -3456,16 +3463,12 @@ dependencies = [ [[package]] name = "roc_std" version = "0.1.0" -dependencies = [ - "libc", -] [[package]] name = "roc_types" version = "0.1.0" dependencies = [ "indoc 0.3.6", - "inlinable_string", "maplit", "pretty_assertions 0.5.1", "quickcheck 0.8.5", @@ -3473,6 +3476,7 @@ dependencies = [ "roc_collections", "roc_module", "roc_region", + "static_assertions", "ven_ena", ] @@ -3953,7 +3957,6 @@ dependencies = [ "im-rc 14.3.0", "indoc 0.3.6", "inkwell 0.1.0", - "inlinable_string", "libc", "libloading 0.6.7", "maplit", @@ -3989,7 +3992,6 @@ dependencies = [ "im 14.3.0", "im-rc 14.3.0", "indoc 0.3.6", - "inlinable_string", "libc", "libloading 0.6.7", "pretty_assertions 0.5.1", diff --git a/Cargo.toml b/Cargo.toml index 06f10ff1fd..01b160bf9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "compiler/ident", "compiler/region", "compiler/collections", "compiler/module", @@ -32,6 +33,7 @@ members = [ "roc_std", "docs", ] +exclude = [ "ci/bench-runner" ] # Needed to be able to run `cargo run -p roc_cli --no-default-features` - # see www/build.sh for more. # diff --git a/CodeOfConduct.md b/CodeOfConduct.md index d3a4ef15dd..aa99bba432 100644 --- a/CodeOfConduct.md +++ b/CodeOfConduct.md @@ -32,7 +32,7 @@ These are the policies for upholding our community's standards of conduct. If yo In the Roc community we strive to go the extra step to look out for each other. Don't just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if they're off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely. -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 progammers 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. +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. diff --git a/Earthfile b/Earthfile index 73859ea1d1..8fbf742cdf 100644 --- a/Earthfile +++ b/Earthfile @@ -1,4 +1,4 @@ -FROM rust:1.52-slim-buster +FROM rust:1.54-slim-buster WORKDIR /earthbuild prep-debian: @@ -40,7 +40,7 @@ install-zig-llvm-valgrind-clippy-rustfmt: # rustfmt RUN rustup component add rustfmt # criterion - RUN cargo install --git https://github.com/Anton-4/cargo-criterion --branch main + RUN cargo install cargo-criterion # sccache RUN apt -y install libssl-dev RUN cargo install sccache diff --git a/README.md b/README.md index 001b4fbe66..d7ec8900cb 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,19 @@ If you're curious about where the language's name and logo came from, ``` 4. Check out [these tests](https://github.com/rtfeldman/roc/blob/trunk/cli/tests/repl_eval.rs) for examples of using the REPL +### Examples + +Took a look at the `examples` folder, `examples/benchmarks` contains some larger examples. + +Run examples as follows: +1. Navigate to `/examples` +2. Run with: +``` +cargo run hello-world/Hello.roc +``` +Some examples like `examples/benchmarks/NQueens.roc` require input after running. +For NQueens, input 10 in the terminal and press enter. + ## Applications and Platforms Applications are often built on a *framework.* Typically, both application and framework are written in the same language. diff --git a/ci/bench-runner.sh b/ci/bench-runner.sh deleted file mode 100755 index 0038612c63..0000000000 --- a/ci/bench-runner.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env bash - -# script to return exit code 1 if benchmarks have regressed - -# benchmark trunk -ulimit -s unlimited -cd bench-folder-trunk -./target/release/deps/time_bench --bench -cd .. - -# move benchmark results so they can be compared later -cp -r bench-folder-trunk/target/criterion bench-folder-branch/target/ - -cd bench-folder-branch - -LOG_FILE="bench_log.txt" -touch $LOG_FILE - -FULL_CMD=" ./target/release/deps/time_bench --bench" -echo $FULL_CMD - -script -efq $LOG_FILE -c "$FULL_CMD" -EXIT_CODE=$? - -if grep -q "regressed" "$LOG_FILE"; then - echo "" - echo "" - echo "------<<<<<<>>>>>>------" - echo "Benchmark detected regression. Running benchmark again to confirm..." - echo "------<<<<<<>>>>>>------" - echo "" - echo "" - - # delete criterion folder to compare to trunk only - rm -rf ./target/criterion - # copy benchmark data from trunk again - cp -r ../bench-folder-trunk/target/criterion ./target - - rm $LOG_FILE - touch $LOG_FILE - - script -efq $LOG_FILE -c "$FULL_CMD" - EXIT_CODE=$? - - if grep -q "regressed" "$LOG_FILE"; then - echo "" - echo "" - echo "------<<<<<>>>>>------" - echo "Benchmarks were run twice and a regression was detected both times." - echo "------<<<<<>>>>>------" - echo "" - echo "" - exit 1 - else - echo "Benchmarks were run twice and a regression was detected on one run. We assume this was a fluke." - exit 0 - fi -else - echo "" - echo "" - echo "------<<<<<>>>>>------" - echo "Benchmark execution failed with exit code: $EXIT_CODE." - echo "------<<<<<>>>>>------" - echo "" - echo "" - exit $EXIT_CODE -fi \ No newline at end of file diff --git a/examples/cli/platform/Cargo.lock b/ci/bench-runner/Cargo.lock similarity index 52% rename from examples/cli/platform/Cargo.lock rename to ci/bench-runner/Cargo.lock index 551e2f492b..e0517c28db 100644 --- a/examples/cli/platform/Cargo.lock +++ b/ci/bench-runner/Cargo.lock @@ -3,22 +3,59 @@ version = 3 [[package]] -name = "base64" -version = "0.13.0" +name = "aho-corasick" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bench-runner" +version = "0.1.0" +dependencies = [ + "clap", + "data-encoding", + "is_executable", + "regex", + "ring", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bumpalo" -version = "3.6.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" +checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" [[package]] name = "cc" -version = "1.0.67" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" +checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" [[package]] name = "cfg-if" @@ -27,74 +64,91 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "chunked_transfer" -version = "1.4.0" +name = "clap" +version = "3.0.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" +checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim", + "termcolor", + "textwrap", + "unicode-width", + "vec_map", +] [[package]] -name = "errno" -version = "0.2.7" +name = "clap_derive" +version = "3.0.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68f2fb9cae9d37c9b2b3584aba698a2e97f72d7aef7b9f7aa71d8b54ce46fe" +checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ - "errno-dragonfly", "libc", +] + +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "is_executable" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa9acdc6d67b75e626ad644734e8bc6df893d9cd2a834129065d3dd6158ea9c8" +dependencies = [ "winapi", ] -[[package]] -name = "errno-dragonfly" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" -dependencies = [ - "gcc", - "libc", -] - -[[package]] -name = "form_urlencoded" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" -dependencies = [ - "matches", - "percent-encoding", -] - -[[package]] -name = "gcc" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" - -[[package]] -name = "host" -version = "0.1.0" -dependencies = [ - "errno", - "libc", - "roc_std", - "ureq", -] - -[[package]] -name = "idna" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "js-sys" -version = "0.3.50" +version = "0.3.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" +checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062" dependencies = [ "wasm-bindgen", ] @@ -107,9 +161,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.92" +version = "0.2.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714" +checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" [[package]] name = "log" @@ -121,28 +175,52 @@ dependencies = [ ] [[package]] -name = "matches" -version = "0.1.8" +name = "memchr" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" [[package]] name = "once_cell" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] -name = "percent-encoding" -version = "2.1.0" +name = "os_str_bytes" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85" + +[[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", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] [[package]] name = "proc-macro2" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" +checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" dependencies = [ "unicode-xid", ] @@ -156,6 +234,23 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + [[package]] name = "ring" version = "0.16.20" @@ -171,36 +266,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "roc_std" -version = "0.1.0" -dependencies = [ - "libc", -] - -[[package]] -name = "rustls" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b" -dependencies = [ - "base64", - "log", - "ring", - "sct", - "webpki", -] - -[[package]] -name = "sct" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "spin" version = "0.5.2" @@ -208,10 +273,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] -name = "syn" -version = "1.0.68" +name = "strsim" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ce15dd3ed8aa2f8eeac4716d6ef5ab58b6b9256db41d7e1a0224c2788e8fd87" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" dependencies = [ "proc-macro2", "quote", @@ -219,43 +290,40 @@ dependencies = [ ] [[package]] -name = "tinyvec" -version = "1.2.0" +name = "termcolor" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" dependencies = [ - "tinyvec_macros", + "winapi-util", ] [[package]] -name = "tinyvec_macros" -version = "0.1.0" +name = "textwrap" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "unicode-bidi" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" dependencies = [ - "matches", + "unicode-width", ] [[package]] -name = "unicode-normalization" -version = "0.1.17" +name = "unicode-segmentation" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" -dependencies = [ - "tinyvec", -] +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "untrusted" @@ -264,38 +332,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] -name = "ureq" -version = "2.1.0" +name = "vec_map" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fbeb1aabb07378cf0e084971a74f24241273304653184f54cdce113c0d7df1b" -dependencies = [ - "base64", - "chunked_transfer", - "log", - "once_cell", - "rustls", - "url", - "webpki", - "webpki-roots", -] +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] -name = "url" -version = "2.2.1" +name = "version_check" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" -dependencies = [ - "form_urlencoded", - "idna", - "matches", - "percent-encoding", -] +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] name = "wasm-bindgen" -version = "0.2.73" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" +checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -303,9 +355,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.73" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" +checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" dependencies = [ "bumpalo", "lazy_static", @@ -318,9 +370,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.73" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" +checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -328,9 +380,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.73" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" +checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" dependencies = [ "proc-macro2", "quote", @@ -341,39 +393,20 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.73" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" +checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f" [[package]] name = "web-sys" -version = "0.3.50" +version = "0.3.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" +checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582" dependencies = [ "js-sys", "wasm-bindgen", ] -[[package]] -name = "webpki" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" -dependencies = [ - "webpki", -] - [[package]] name = "winapi" version = "0.3.9" @@ -390,6 +423,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/ci/bench-runner/Cargo.toml b/ci/bench-runner/Cargo.toml new file mode 100644 index 0000000000..31fcd9cdda --- /dev/null +++ b/ci/bench-runner/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "bench-runner" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = "3.0.0-beta.2" +regex = "1.5.4" +is_executable = "1.0.1" +ring = "0.16.20" +data-encoding = "2.3.2" \ No newline at end of file diff --git a/ci/bench-runner/src/main.rs b/ci/bench-runner/src/main.rs new file mode 100644 index 0000000000..b30e692541 --- /dev/null +++ b/ci/bench-runner/src/main.rs @@ -0,0 +1,256 @@ +use clap::{AppSettings, Clap}; +use data_encoding::HEXUPPER; +use is_executable::IsExecutable; +use regex::Regex; +use ring::digest::{Context, Digest, SHA256}; +use std::fs::File; +use std::io::Read; +use std::{ + collections::{HashMap, HashSet, VecDeque}, + io::{self, BufRead, BufReader}, + path::Path, + process::{self, Command, Stdio}, +}; + +const BENCH_FOLDER_TRUNK: &str = "bench-folder-trunk"; +const BENCH_FOLDER_BRANCH: &str = "bench-folder-branch"; + +fn main() { + let optional_args: OptionalArgs = OptionalArgs::parse(); + + if Path::new(BENCH_FOLDER_TRUNK).exists() && Path::new(BENCH_FOLDER_BRANCH).exists() { + delete_old_bench_results(); + + if optional_args.check_executables_changed { + println!("Doing a test run to verify benchmarks are working correctly and generate executables."); + + std::env::set_var("BENCH_DRY_RUN", "1"); + + do_benchmark("trunk"); + do_benchmark("branch"); + + std::env::set_var("BENCH_DRY_RUN", "0"); + + if check_if_bench_executables_changed() { + println!( + "Comparison of sha256 of executables reveals changes, doing full benchmarks..." + ); + + let all_regressed_benches = do_all_benches(optional_args.nr_repeat_benchmarks); + + finish(all_regressed_benches, optional_args.nr_repeat_benchmarks); + } else { + println!("No benchmark executables have changed"); + } + } else { + let all_regressed_benches = do_all_benches(optional_args.nr_repeat_benchmarks); + + finish(all_regressed_benches, optional_args.nr_repeat_benchmarks); + } + } else { + eprintln!( + r#"I can't find bench-folder-trunk and bench-folder-branch from the current directory. + I should be executed from the repo root. + Use `./ci/safe-earthly.sh --build-arg BENCH_SUFFIX=trunk +prep-bench-folder` to generate bench-folder-trunk. + Use `./ci/safe-earthly.sh +prep-bench-folder` to generate bench-folder-branch."# + ); + + process::exit(1) + } +} + +fn finish(all_regressed_benches: HashSet, nr_repeat_benchmarks: usize) { + if !all_regressed_benches.is_empty() { + eprintln!( + r#" + + FAILED: The following benchmarks have shown a regression {:?} times: {:?} + + "#, + nr_repeat_benchmarks, all_regressed_benches + ); + + process::exit(1); + } +} + +// returns all benchmarks that have regressed +fn do_all_benches(nr_repeat_benchmarks: usize) -> HashSet { + delete_old_bench_results(); + do_benchmark("trunk"); + let mut all_regressed_benches = do_benchmark("branch"); + + // if no benches regressed this round, abort early + if all_regressed_benches.is_empty() { + return HashSet::new(); + } + + for _ in 1..nr_repeat_benchmarks { + delete_old_bench_results(); + do_benchmark("trunk"); + let regressed_benches = do_benchmark("branch"); + + // if no benches regressed this round, abort early + if regressed_benches.is_empty() { + return HashSet::new(); + } + + all_regressed_benches = all_regressed_benches + .intersection(®ressed_benches) + .map(|bench_name_str| bench_name_str.to_owned()) + .collect(); + } + + all_regressed_benches +} + +// returns Vec with names of regressed benchmarks +fn do_benchmark(branch_name: &'static str) -> HashSet { + let mut cmd_child = Command::new(format!( + "./bench-folder-{}/target/release/deps/time_bench", + branch_name + )) + .args(&["--bench", "--noplot"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap_or_else(|_| panic!("Failed to benchmark {}.", branch_name)); + + let stdout = cmd_child.stdout.as_mut().unwrap(); + let stdout_reader = BufReader::new(stdout); + let stdout_lines = stdout_reader.lines(); + + let mut regressed_benches: HashSet = HashSet::new(); + + let mut last_three_lines_queue: VecDeque = VecDeque::with_capacity(3); + let bench_name_regex = Regex::new(r#"".*""#).expect("Failed to build regex"); + + for line in stdout_lines { + let line_str = line.expect("Failed to get output from banchmark command."); + + if line_str.contains("regressed") { + let regressed_bench_name_line = last_three_lines_queue.get(2).expect( + "Failed to get line that contains benchmark name from last_three_lines_queue.", + ); + + let regex_match = bench_name_regex.find(regressed_bench_name_line).expect("This line should hoave the benchmark name between double quotes but I could not match it"); + + regressed_benches.insert(regex_match.as_str().to_string().replace("\"", "")); + } + + last_three_lines_queue.push_front(line_str.clone()); + + println!("bench {:?}: {:?}", branch_name, line_str); + } + + regressed_benches +} + +fn delete_old_bench_results() { + remove("target/criterion"); +} + +// does not error if fileOrFolder does not exist (-f flag) +fn remove(file_or_folder: &str) { + Command::new("rm") + .args(&["-rf", file_or_folder]) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .output() + .unwrap_or_else(|_| panic!("Something went wrong trying to remove {}", file_or_folder)); +} + +#[derive(Clap)] +#[clap(setting = AppSettings::ColoredHelp)] +struct OptionalArgs { + /// How many times to repeat the benchmarks. A single benchmark has to fail every for a regression to be reported. + #[clap(long, default_value = "3")] + nr_repeat_benchmarks: usize, + /// Do not run full benchmarks if no benchmark executable has changed + #[clap(long)] + check_executables_changed: bool, +} + +fn sha256_digest(mut reader: R) -> Result { + let mut context = Context::new(&SHA256); + let mut buffer = [0; 1024]; + + loop { + let count = reader.read(&mut buffer)?; + if count == 0 { + break; + } + context.update(&buffer[..count]); + } + + Ok(context.finish()) +} + +fn sha_file(file_path: &Path) -> Result { + let input = File::open(file_path)?; + let reader = BufReader::new(input); + let digest = sha256_digest(reader)?; + + Ok(HEXUPPER.encode(digest.as_ref())) +} + +fn calc_hashes_for_folder(benches_path_str: &str) -> HashMap { + let benches_path = Path::new(benches_path_str); + let all_bench_files = + std::fs::read_dir(benches_path).expect("Failed to create iterator for files in dir."); + + let non_src_files = all_bench_files + .into_iter() + .map(|file_res| { + file_res + .expect("Failed to get DirEntry from ReadDir all_bench_files") + .file_name() + .into_string() + .expect("Failed to create String from OsString for file_name.") + }) + .filter(|file_name_str| !file_name_str.contains(".roc")); + + let mut files_w_sha = HashMap::new(); + + for file_name in non_src_files { + let full_path_str = [benches_path_str, &file_name].join(""); + let full_path = Path::new(&full_path_str); + + if full_path.is_executable() { + files_w_sha.insert( + file_name.clone(), + sha_file(full_path).expect("Failed to calculate sha of file"), + ); + } + } + + files_w_sha +} + +fn check_if_bench_executables_changed() -> bool { + let bench_folder_str = "/examples/benchmarks/"; + + let trunk_benches_path_str = [BENCH_FOLDER_TRUNK, bench_folder_str].join(""); + let trunk_bench_hashes = calc_hashes_for_folder(&trunk_benches_path_str); + + let branch_benches_path_str = [BENCH_FOLDER_BRANCH, bench_folder_str].join(""); + let branch_bench_hashes = calc_hashes_for_folder(&branch_benches_path_str); + + if trunk_bench_hashes.keys().len() == branch_bench_hashes.keys().len() { + for key in trunk_bench_hashes.keys() { + if let Some(trunk_hash_val) = trunk_bench_hashes.get(key) { + if let Some(branch_hash_val) = branch_bench_hashes.get(key) { + if !trunk_hash_val.eq(branch_hash_val) { + return true; + } + } else { + return true; + } + } + } + + false + } else { + true + } +} diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 531e9a76ee..8288d26d6f 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -57,13 +57,12 @@ roc_reporting = { path = "../compiler/reporting" } roc_editor = { path = "../editor", optional = true } # 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" } -const_format = "0.2.8" +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"] } -inlinable_string = "0.1" libc = "0.2" libloading = "0.6" diff --git a/cli/benches/time_bench.rs b/cli/benches/time_bench.rs index d86230a669..56395acc75 100644 --- a/cli/benches/time_bench.rs +++ b/cli/benches/time_bench.rs @@ -1,15 +1,28 @@ +use std::time::Duration; + use cli_utils::bench_utils::{ bench_cfold, bench_deriv, bench_nqueens, bench_quicksort, bench_rbtree_ck, bench_rbtree_delete, }; -use criterion::{ - criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion, SamplingMode, -}; +use criterion::{measurement::WallTime, BenchmarkGroup, Criterion, SamplingMode}; fn bench_group_wall_time(c: &mut Criterion) { let mut group = c.benchmark_group("bench-group_wall-time"); - // calculate statistics based on a fixed(flat) 300 runs + // calculate statistics based on a fixed(flat) x runs group.sampling_mode(SamplingMode::Flat); - group.sample_size(300); + + let default_nr_of_runs = 200; + let nr_of_runs = match std::env::var("BENCH_DRY_RUN") { + Ok(val) => { + if val == "1" { + 10 // minimum value allowed by criterion + } else { + default_nr_of_runs + } + } + Err(_) => default_nr_of_runs, + }; + + group.sample_size(nr_of_runs); let bench_funcs: Vec>) -> ()> = vec![ bench_nqueens, // queens 11 @@ -27,5 +40,32 @@ fn bench_group_wall_time(c: &mut Criterion) { group.finish(); } -criterion_group!(benches, bench_group_wall_time); -criterion_main!(benches); +// use short warm up and measurement time on dry run +fn make_config() -> Criterion { + let default_config = Criterion::default(); + + match std::env::var("BENCH_DRY_RUN") { + Ok(val) => { + if val == "1" { + default_config + .warm_up_time(Duration::new(1, 0)) + .measurement_time(Duration::new(1, 0)) + } else { + default_config + } + } + Err(_) => default_config, + } +} + +fn all_benches() { + let mut criterion: Criterion<_> = make_config().configure_from_args(); + + bench_group_wall_time(&mut criterion); +} + +fn main() { + all_benches(); + + Criterion::default().configure_from_args().final_summary(); +} diff --git a/cli/cli_utils/Cargo.toml b/cli/cli_utils/Cargo.toml index d80d17e3c8..faf1da7fa9 100644 --- a/cli/cli_utils/Cargo.toml +++ b/cli/cli_utils/Cargo.toml @@ -21,3 +21,4 @@ serde = { version = "1.0", features = ["derive"] } serde-xml-rs = "0.4" strip-ansi-escapes = "0.1" tempfile = "3.1.0" +rlimit = "0.6.2" diff --git a/cli/cli_utils/src/bench_utils.rs b/cli/cli_utils/src/bench_utils.rs index d74e3f5bad..146f500380 100644 --- a/cli/cli_utils/src/bench_utils.rs +++ b/cli/cli_utils/src/bench_utils.rs @@ -1,5 +1,6 @@ use crate::helpers::{example_file, run_cmd, run_roc}; use criterion::{black_box, measurement::Measurement, BenchmarkGroup}; +use rlimit::{setrlimit, Resource}; use std::path::Path; fn exec_bench_w_input( @@ -34,11 +35,17 @@ fn check_cmd_output( executable_filename: &str, expected_ending: &str, ) { - let out = run_cmd( - file.with_file_name(executable_filename).to_str().unwrap(), - stdin_str, - &[], - ); + let cmd_str = file + .with_file_name(executable_filename) + .to_str() + .unwrap() + .to_string(); + + if cmd_str.contains("cfold") { + increase_stack_limit(); + } + + let out = run_cmd(&cmd_str, &[stdin_str], &[]); if !&out.stdout.ends_with(expected_ending) { panic!( @@ -55,31 +62,41 @@ fn bench_cmd( executable_filename: &str, bench_group_opt: Option<&mut BenchmarkGroup>, ) { + let cmd_str = file + .with_file_name(executable_filename) + .to_str() + .unwrap() + .to_string(); + + if cmd_str.contains("cfold") { + increase_stack_limit(); + } + if let Some(bench_group) = bench_group_opt { bench_group.bench_function(&format!("Benchmarking {:?}", executable_filename), |b| { - b.iter(|| { - run_cmd( - black_box(file.with_file_name(executable_filename).to_str().unwrap()), - black_box(stdin_str), - &[], - ) - }) + b.iter(|| run_cmd(black_box(&cmd_str), black_box(&[stdin_str]), &[])) }); } else { run_cmd( black_box(file.with_file_name(executable_filename).to_str().unwrap()), - black_box(stdin_str), + black_box(&[stdin_str]), &[], ); } } +fn increase_stack_limit() { + let new_stack_limit = 8192 * 100000; + setrlimit(Resource::STACK, new_stack_limit, new_stack_limit) + .expect("Failed to increase stack limit."); +} + pub fn bench_nqueens(bench_group_opt: Option<&mut BenchmarkGroup>) { exec_bench_w_input( &example_file("benchmarks", "NQueens.roc"), "11", "nqueens", - "2680\n", + "2680\n", //2680-14200 bench_group_opt, ); } diff --git a/cli/cli_utils/src/helpers.rs b/cli/cli_utils/src/helpers.rs index 6f45d89a68..11a6a404f3 100644 --- a/cli/cli_utils/src/helpers.rs +++ b/cli/cli_utils/src/helpers.rs @@ -1,5 +1,4 @@ extern crate bumpalo; -extern crate inlinable_string; extern crate roc_collections; extern crate roc_load; extern crate roc_module; @@ -65,7 +64,7 @@ pub fn run_roc(args: &[&str]) -> Out { } #[allow(dead_code)] -pub fn run_cmd(cmd_name: &str, stdin_str: &str, args: &[&str]) -> Out { +pub fn run_cmd(cmd_name: &str, stdin_vals: &[&str], args: &[&str]) -> Out { let mut cmd = Command::new(cmd_name); for arg in args { @@ -81,9 +80,12 @@ pub fn run_cmd(cmd_name: &str, stdin_str: &str, args: &[&str]) -> Out { { let stdin = child.stdin.as_mut().expect("Failed to open stdin"); - stdin - .write_all(stdin_str.as_bytes()) - .expect("Failed to write to stdin"); + + for stdin_str in stdin_vals { + stdin + .write_all(stdin_str.as_bytes()) + .expect("Failed to write to stdin"); + } } let output = child @@ -98,7 +100,7 @@ pub fn run_cmd(cmd_name: &str, stdin_str: &str, args: &[&str]) -> Out { } #[allow(dead_code)] -pub fn run_with_valgrind(stdin_str: &str, args: &[&str]) -> (Out, String) { +pub fn run_with_valgrind(stdin_vals: &[&str], args: &[&str]) -> (Out, String) { //TODO: figure out if there is a better way to get the valgrind executable. let mut cmd = Command::new("valgrind"); let named_tempfile = @@ -142,9 +144,12 @@ pub fn run_with_valgrind(stdin_str: &str, args: &[&str]) -> (Out, String) { { let stdin = child.stdin.as_mut().expect("Failed to open stdin"); - stdin - .write_all(stdin_str.as_bytes()) - .expect("Failed to write to stdin"); + + for stdin_str in stdin_vals { + stdin + .write_all(stdin_str.as_bytes()) + .expect("Failed to write to stdin"); + } } let output = child @@ -228,7 +233,7 @@ pub fn extract_valgrind_errors(xml: &str) -> Result, serde_xm } #[allow(dead_code)] -pub fn example_dir(dir_name: &str) -> PathBuf { +pub fn root_dir() -> PathBuf { let mut path = env::current_exe().ok().unwrap(); // Get rid of the filename in target/debug/deps/cli_run-99c65e4e9a1fbd06 @@ -243,6 +248,13 @@ pub fn example_dir(dir_name: &str) -> PathBuf { path.pop(); path.pop(); + path +} + +#[allow(dead_code)] +pub fn examples_dir(dir_name: &str) -> PathBuf { + let mut path = root_dir(); + // Descend into examples/{dir_name} path.push("examples"); path.push(dir_name); @@ -252,7 +264,7 @@ pub fn example_dir(dir_name: &str) -> PathBuf { #[allow(dead_code)] pub fn example_file(dir_name: &str, file_name: &str) -> PathBuf { - let mut path = example_dir(dir_name); + let mut path = examples_dir(dir_name); path.push(file_name); @@ -261,19 +273,7 @@ pub fn example_file(dir_name: &str, file_name: &str) -> PathBuf { #[allow(dead_code)] pub fn fixtures_dir(dir_name: &str) -> PathBuf { - let mut path = env::current_exe().ok().unwrap(); - - // Get rid of the filename in target/debug/deps/cli_run-99c65e4e9a1fbd06 - path.pop(); - - // If we're in deps/ get rid of deps/ in target/debug/deps/ - if path.ends_with("deps") { - path.pop(); - } - - // Get rid of target/debug/ so we're back at the project root - path.pop(); - path.pop(); + let mut path = root_dir(); // Descend into cli/tests/fixtures/{dir_name} path.push("cli"); diff --git a/cli/src/build.rs b/cli/src/build.rs index d75bf0361b..6a498fc5f5 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -65,7 +65,7 @@ pub fn build_file<'a>( }; let loaded = roc_load::file::load_and_monomorphize( - &arena, + arena, roc_file_path.clone(), stdlib, src_dir.as_path(), @@ -128,11 +128,11 @@ pub fn build_file<'a>( let cwd = roc_file_path.parent().unwrap(); let binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows let code_gen_timing = program::gen_from_mono_module( - &arena, + arena, loaded, &roc_file_path, Triple::host(), - &app_o_file, + app_o_file, opt_level, emit_debug_info, ); diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 1cd2ea0eab..7eb98bda36 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,6 +1,9 @@ #[macro_use] extern crate clap; +#[macro_use] +extern crate const_format; + use build::{BuildOutcome, BuiltFile}; use bumpalo::Bump; use clap::{App, AppSettings, Arg, ArgMatches}; @@ -32,9 +35,10 @@ pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP"; pub fn build_app<'a>() -> App<'a> { let app = App::new("roc") - .version(crate_version!()) + .version(concatcp!(crate_version!(), "\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 program") + .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") @@ -43,7 +47,7 @@ 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.)") + .help("Optimize your compiled Roc program to run faster. (Optimization takes time to complete.)") .required(false), ) .arg( @@ -60,7 +64,7 @@ pub fn build_app<'a>() -> App<'a> { ) ) .subcommand(App::new(CMD_RUN) - .about("Build and run a program") + .about("DEPRECATED - now use `roc [FILE]` instead of `roc run [FILE]`") .setting(AppSettings::TrailingVarArg) .arg( Arg::with_name(FLAG_OPTIMIZE) @@ -76,7 +80,7 @@ pub fn build_app<'a>() -> App<'a> { ) .arg( Arg::with_name(ROC_FILE) - .help("The .roc file of an app to build and run") + .help("The .roc file of an app to run") .required(true), ) .arg( @@ -94,10 +98,36 @@ pub fn build_app<'a>() -> App<'a> { .arg(Arg::with_name(DIRECTORY_OR_FILES) .index(1) .multiple(true) - .required(true) + .required(false) .help("The directory or files to build documentation for") ) + ) + .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.)") + .requires(ROC_FILE) + .required(false), + ) + .arg( + Arg::with_name(FLAG_DEBUG) + .long(FLAG_DEBUG) + .help("Store LLVM debug information in the generated program") + .requires(ROC_FILE) + .required(false), + ) + .arg( + Arg::with_name(ROC_FILE) + .help("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") + .requires(ROC_FILE) + .multiple(true), ); if cfg!(feature = "editor") { @@ -151,8 +181,7 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io:: LinkType::Executable }; - let path = Path::new(filename).canonicalize().unwrap(); - let src_dir = path.parent().unwrap().canonicalize().unwrap(); + let path = Path::new(filename); // Spawn the root task let path = path.canonicalize().unwrap_or_else(|err| { @@ -173,6 +202,7 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io:: } }); + let src_dir = path.parent().unwrap().canonicalize().unwrap(); let res_binary_path = build_file( &arena, target, @@ -215,7 +245,7 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io:: // Forward all the arguments after the .roc file argument // to the new process. This way, you can do things like: // - // roc run app.roc foo bar baz + // roc app.roc foo bar baz // // ...and have it so that app.roc will receive only `foo`, // `bar`, and `baz` as its arguments. @@ -263,16 +293,16 @@ fn roc_run(cmd: &mut Command) -> io::Result { .spawn() .unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err)) .wait() - .expect("TODO gracefully handle block_on failing when roc run spawns a subprocess for the compiled app"); + .expect("TODO gracefully handle block_on failing when `roc` spawns a subprocess for the compiled app"); - // `roc run` exits with the same status code as the app it ran. + // `roc [FILE]` exits with the same status code as the app it ran. // // If you want to know whether there were compilation problems // via status code, use either `roc build` or `roc check` instead! match exit_status.code() { Some(code) => Ok(code), None => { - todo!("TODO gracefully handle the roc run subprocess terminating with a signal."); + todo!("TODO gracefully handle the `roc [FILE]` subprocess terminating with a signal."); } } } diff --git a/cli/src/main.rs b/cli/src/main.rs index fccf700c96..43cd182217 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -9,6 +9,7 @@ use target_lexicon::Triple; #[cfg(feature = "llvm")] use roc_cli::build; +use std::ffi::{OsStr, OsString}; #[cfg(not(feature = "llvm"))] fn build(_target: &Triple, _matches: &clap::ArgMatches, _config: BuildConfig) -> io::Result { @@ -20,10 +21,23 @@ fn main() -> io::Result<()> { let exit_code = match matches.subcommand_name() { None => { - launch_editor(&[])?; + match matches.index_of(ROC_FILE) { + Some(arg_index) => { + let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is! - // rustc couldn't infer the error type here - Result::::Ok(0) + build( + &Triple::host(), + &matches, + BuildConfig::BuildAndRun { roc_file_arg_index }, + ) + } + + None => { + launch_editor(&[])?; + + Ok(0) + } + } } Some(CMD_BUILD) => Ok(build( &Triple::host(), @@ -31,14 +45,15 @@ fn main() -> io::Result<()> { BuildConfig::BuildOnly, )?), Some(CMD_RUN) => { - let subcmd_matches = matches.subcommand_matches(CMD_RUN).unwrap(); - let roc_file_arg_index = subcmd_matches.index_of(ROC_FILE).unwrap() + 1; // Not sure why this +1 is necessary, but it is! + // 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(build( - &Triple::host(), - subcmd_matches, - BuildConfig::BuildAndRun { roc_file_arg_index }, - )?) + Ok(1) } Some(CMD_REPL) => { repl::main()?; @@ -68,19 +83,34 @@ fn main() -> io::Result<()> { Ok(0) } Some(CMD_DOCS) => { - let values = matches + let maybe_values = matches .subcommand_matches(CMD_DOCS) .unwrap() - .values_of_os(DIRECTORY_OR_FILES) - .unwrap(); + .values_of_os(DIRECTORY_OR_FILES); + + let mut values: Vec = Vec::new(); + + match maybe_values { + None => { + let mut os_string_values: Vec = Vec::new(); + read_all_roc_files(&OsStr::new("./").to_os_string(), &mut os_string_values)?; + for os_string in os_string_values { + values.push(os_string); + } + } + Some(os_values) => { + for os_str in os_values { + values.push(os_str.to_os_string()); + } + } + } let mut roc_files = Vec::new(); // Populate roc_files for os_str in values { - let metadata = fs::metadata(os_str)?; - - roc_files_recursive(os_str, metadata.file_type(), &mut roc_files)?; + let metadata = fs::metadata(os_str.clone())?; + roc_files_recursive(os_str.as_os_str(), metadata.file_type(), &mut roc_files)?; } docs(roc_files); @@ -93,6 +123,26 @@ fn main() -> io::Result<()> { std::process::exit(exit_code); } +fn read_all_roc_files( + dir: &OsString, + mut roc_file_paths: &mut Vec, +) -> Result<(), std::io::Error> { + let entries = fs::read_dir(dir)?; + + for entry in entries { + let path = entry?.path(); + + if path.is_dir() { + read_all_roc_files(&path.into_os_string(), &mut roc_file_paths)?; + } else if path.extension().and_then(OsStr::to_str) == Some("roc") { + let file_path = path.into_os_string(); + roc_file_paths.push(file_path); + } + } + + Ok(()) +} + fn roc_files_recursive>( path: P, file_type: FileType, diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 7d150a4756..d97d8087c6 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -1,17 +1,15 @@ use bumpalo::collections::Vec; use bumpalo::Bump; use libloading::Library; -use roc_collections::all::MutMap; use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type}; -use roc_module::ident::{Lowercase, TagName}; +use roc_module::ident::TagName; use roc_module::operator::CalledVia; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_mono::ir::ProcLayout; use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant}; use roc_parse::ast::{AssignedField, Expr, StrLiteral}; use roc_region::all::{Located, Region}; -use roc_types::subs::{Content, FlatType, Subs, Variable}; -use roc_types::types::RecordField; +use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable}; struct Env<'a, 'env> { arena: &'a Bump, @@ -153,27 +151,38 @@ fn jit_to_ast_help<'a>( Layout::Struct(field_layouts) => { let ptr_to_ast = |ptr: *const u8| match content { Content::Structure(FlatType::Record(fields, _)) => { - Ok(struct_to_ast(env, ptr, field_layouts, fields)) - } - Content::Structure(FlatType::EmptyRecord) => { - Ok(struct_to_ast(env, ptr, field_layouts, &MutMap::default())) + Ok(struct_to_ast(env, ptr, field_layouts, *fields)) } + Content::Structure(FlatType::EmptyRecord) => Ok(struct_to_ast( + env, + ptr, + field_layouts, + RecordFields::empty(), + )), Content::Structure(FlatType::TagUnion(tags, _)) => { debug_assert_eq!(tags.len(), 1); - let (tag_name, payload_vars) = tags.iter().next().unwrap(); + let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); Ok(single_tag_union_to_ast( env, ptr, field_layouts, - tag_name.clone(), + tag_name, payload_vars, )) } - Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => Ok( - single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), &[]), - ), + Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => { + let tag_name = &env.subs[*tag_name]; + + Ok(single_tag_union_to_ast( + env, + ptr, + field_layouts, + tag_name, + &[], + )) + } Content::Structure(FlatType::Func(_, _, _)) => { // a function with a struct as the closure environment Err(ToAstProblem::FunctionLayout) @@ -205,8 +214,13 @@ fn jit_to_ast_help<'a>( Content::Structure(FlatType::TagUnion(tags, _)) => { debug_assert_eq!(union_layouts.len(), tags.len()); - let tags_vec: std::vec::Vec<(TagName, std::vec::Vec)> = - tags.iter().map(|(a, b)| (a.clone(), b.clone())).collect(); + let tags_vec: std::vec::Vec<(TagName, std::vec::Vec)> = tags + .unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION) + .map(|(a, b)| (a.clone(), b.to_vec())) + .collect(); + + let tags_map: roc_collections::all::MutMap<_, _> = + tags_vec.iter().cloned().collect(); let union_variant = union_sorted_tags_help(env.arena, tags_vec, None, env.subs); @@ -245,6 +259,11 @@ fn jit_to_ast_help<'a>( Builtin::Int16 => { *(ptr.add(offset as usize) as *const i16) as i64 } + Builtin::Int64 => { + // used by non-recursive unions at the + // moment, remove if that is no longer the case + *(ptr.add(offset as usize) as *const i64) as i64 + } _ => unreachable!("invalid tag id layout"), }; @@ -256,7 +275,7 @@ fn jit_to_ast_help<'a>( let loc_tag_expr = &*env.arena.alloc(Located::at_zero(tag_expr)); - let variables = &tags[tag_name]; + let variables = &tags_map[tag_name]; debug_assert_eq!(arg_layouts.len(), variables.len()); @@ -289,7 +308,7 @@ fn jit_to_ast_help<'a>( let loc_tag_expr = &*env.arena.alloc(Located::at_zero(tag_expr)); - let variables = &tags[tag_name]; + let variables = &tags_map[tag_name]; // because the arg_layouts include the tag ID, it is one longer debug_assert_eq!( @@ -319,9 +338,9 @@ fn jit_to_ast_help<'a>( todo!("print recursive tag unions in the REPL") } Content::Alias(_, _, actual) => { - let content = env.subs.get_without_compacting(*actual).content; + let content = env.subs.get_content_without_compacting(*actual); - jit_to_ast_help(env, lib, main_fn_name, layout, &content) + jit_to_ast_help(env, lib, main_fn_name, layout, content) } other => unreachable!("Weird content for Union layout: {:?}", other), } @@ -333,8 +352,6 @@ fn jit_to_ast_help<'a>( | Layout::RecursivePointer => { todo!("add support for rendering recursive tag unions in the REPL") } - - Layout::Closure(_, _, _) => Err(ToAstProblem::FunctionLayout), } } @@ -342,11 +359,11 @@ fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> { match tag_name { TagName::Global(_) => Expr::GlobalTag( env.arena - .alloc_str(&tag_name.as_string(env.interns, env.home)), + .alloc_str(&tag_name.as_ident_str(env.interns, env.home)), ), TagName::Private(_) => Expr::PrivateTag( env.arena - .alloc_str(&tag_name.as_string(env.interns, env.home)), + .alloc_str(&tag_name.as_ident_str(env.interns, env.home)), ), TagName::Closure(_) => unreachable!("User cannot type this"), } @@ -379,8 +396,8 @@ fn ptr_to_ast<'a>( num_to_ast(env, number_literal_to_ast(env.arena, num), content) } - Layout::Builtin(Builtin::Usize) => { - let num = unsafe { *(ptr as *const usize) }; + Layout::Builtin(Builtin::Int8) => { + let num = unsafe { *(ptr as *const i8) }; num_to_ast(env, number_literal_to_ast(env.arena, num), content) } @@ -391,6 +408,11 @@ fn ptr_to_ast<'a>( bool_to_ast(env, num, content) } + Layout::Builtin(Builtin::Usize) => { + let num = unsafe { *(ptr as *const usize) }; + + num_to_ast(env, number_literal_to_ast(env.arena, num), content) + } Layout::Builtin(Builtin::Float64) => { let num = unsafe { *(ptr as *const f64) }; @@ -420,19 +442,20 @@ fn ptr_to_ast<'a>( } Layout::Struct(field_layouts) => match content { Content::Structure(FlatType::Record(fields, _)) => { - struct_to_ast(env, ptr, field_layouts, fields) + struct_to_ast(env, ptr, field_layouts, *fields) } Content::Structure(FlatType::TagUnion(tags, _)) => { debug_assert_eq!(tags.len(), 1); - let (tag_name, payload_vars) = tags.iter().next().unwrap(); - single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), payload_vars) + let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); + single_tag_union_to_ast(env, ptr, field_layouts, tag_name, payload_vars) } Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => { - single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), &[]) + let tag_name = &env.subs[*tag_name]; + single_tag_union_to_ast(env, ptr, field_layouts, tag_name, &[]) } Content::Structure(FlatType::EmptyRecord) => { - struct_to_ast(env, ptr, &[], &MutMap::default()) + struct_to_ast(env, ptr, &[], RecordFields::empty()) } other => { unreachable!( @@ -461,9 +484,10 @@ fn list_to_ast<'a>( Content::Structure(FlatType::Apply(Symbol::LIST_LIST, vars)) => { debug_assert_eq!(vars.len(), 1); - let elem_var = *vars.first().unwrap(); + let elem_var_index = vars.into_iter().next().unwrap(); + let elem_var = env.subs[elem_var_index]; - env.subs.get_without_compacting(elem_var).content + env.subs.get_content_without_compacting(elem_var) } other => { unreachable!( @@ -474,14 +498,14 @@ fn list_to_ast<'a>( }; let arena = env.arena; - let mut output = Vec::with_capacity_in(len, &arena); + let mut output = Vec::with_capacity_in(len, arena); let elem_size = elem_layout.stack_size(env.ptr_bytes) as usize; for index in 0..len { let offset_bytes = index * elem_size; let elem_ptr = unsafe { ptr.add(offset_bytes) }; let loc_expr = &*arena.alloc(Located { - value: ptr_to_ast(env, elem_ptr, elem_layout, &elem_content), + value: ptr_to_ast(env, elem_ptr, elem_layout, elem_content), region: Region::zero(), }); @@ -500,14 +524,14 @@ fn single_tag_union_to_ast<'a>( env: &Env<'a, '_>, ptr: *const u8, field_layouts: &'a [Layout<'a>], - tag_name: TagName, + tag_name: &TagName, payload_vars: &[Variable], ) -> Expr<'a> { debug_assert_eq!(field_layouts.len(), payload_vars.len()); let arena = env.arena; - let tag_expr = tag_name_to_expr(env, &tag_name); + let tag_expr = tag_name_to_expr(env, tag_name); let loc_tag_expr = &*arena.alloc(Located::at_zero(tag_expr)); @@ -528,14 +552,14 @@ where { let arena = env.arena; let subs = env.subs; - let mut output = Vec::with_capacity_in(sequence.len(), &arena); + let mut output = Vec::with_capacity_in(sequence.len(), arena); // We'll advance this as we iterate through the fields let mut field_ptr = ptr as *const u8; for (var, layout) in sequence { - let content = subs.get_without_compacting(var).content; - let expr = ptr_to_ast(env, field_ptr, layout, &content); + let content = subs.get_content_without_compacting(var); + let expr = ptr_to_ast(env, field_ptr, layout, content); let loc_expr = Located::at_zero(expr); output.push(&*arena.alloc(loc_expr)); @@ -551,31 +575,25 @@ fn struct_to_ast<'a>( env: &Env<'a, '_>, ptr: *const u8, field_layouts: &'a [Layout<'a>], - fields: &MutMap>, + record_fields: RecordFields, ) -> Expr<'a> { let arena = env.arena; let subs = env.subs; - let mut output = Vec::with_capacity_in(field_layouts.len(), &arena); + let mut output = Vec::with_capacity_in(field_layouts.len(), arena); - // The fields, sorted alphabetically - let mut sorted_fields = { - let mut vec = fields - .iter() - .collect::)>>(); - - vec.sort_by(|(label1, _), (label2, _)| label1.cmp(label2)); - - vec - }; + let sorted_fields: Vec<_> = Vec::from_iter_in( + record_fields.sorted_iterator(env.subs, Variable::EMPTY_RECORD), + env.arena, + ); if sorted_fields.len() == 1 { // this is a 1-field wrapper record around another record or 1-tag tag union - let (label, field) = sorted_fields.pop().unwrap(); + let (label, field) = sorted_fields.into_iter().next().unwrap(); - let inner_content = env.subs.get_without_compacting(field.into_inner()).content; + let inner_content = env.subs.get_content_without_compacting(field.into_inner()); let loc_expr = &*arena.alloc(Located { - value: ptr_to_ast(env, ptr, &Layout::Struct(field_layouts), &inner_content), + value: ptr_to_ast(env, ptr, &Layout::Struct(field_layouts), inner_content), region: Region::zero(), }); @@ -600,10 +618,12 @@ fn struct_to_ast<'a>( // We'll advance this as we iterate through the fields let mut field_ptr = ptr; - for ((label, field), field_layout) in sorted_fields.iter().zip(field_layouts.iter()) { - let content = subs.get_without_compacting(*field.as_inner()).content; + for ((label, field), field_layout) in sorted_fields.into_iter().zip(field_layouts.iter()) { + let var = field.into_inner(); + + let content = subs.get_content_without_compacting(var); let loc_expr = &*arena.alloc(Located { - value: ptr_to_ast(env, field_ptr, field_layout, &content), + value: ptr_to_ast(env, field_ptr, field_layout, content), region: Region::zero(), }); @@ -632,6 +652,36 @@ fn struct_to_ast<'a>( } } +fn unpack_single_element_tag_union(subs: &Subs, tags: UnionTags) -> (&TagName, &[Variable]) { + let (tag_name_index, payload_vars_index) = tags.iter_all().next().unwrap(); + + let tag_name = &subs[tag_name_index]; + let subs_slice = subs[payload_vars_index].as_subs_slice(); + let payload_vars = subs.get_subs_slice(*subs_slice); + + (tag_name, payload_vars) +} + +fn unpack_two_element_tag_union( + subs: &Subs, + tags: UnionTags, +) -> (&TagName, &[Variable], &TagName, &[Variable]) { + let mut it = tags.iter_all(); + let (tag_name_index, payload_vars_index) = it.next().unwrap(); + + let tag_name1 = &subs[tag_name_index]; + let subs_slice = subs[payload_vars_index].as_subs_slice(); + let payload_vars1 = subs.get_subs_slice(*subs_slice); + + let (tag_name_index, payload_vars_index) = it.next().unwrap(); + + let tag_name2 = &subs[tag_name_index]; + let subs_slice = subs[payload_vars_index].as_subs_slice(); + let payload_vars2 = subs.get_subs_slice(*subs_slice); + + (tag_name1, payload_vars1, tag_name2, payload_vars2) +} + fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a> { use Content::*; @@ -643,7 +693,11 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a FlatType::Record(fields, _) => { debug_assert_eq!(fields.len(), 1); - let (label, field) = fields.iter().next().unwrap(); + let (label, field) = fields + .sorted_iterator(env.subs, Variable::EMPTY_RECORD) + .next() + .unwrap(); + let loc_label = Located { value: &*arena.alloc_str(label.as_str()), region: Region::zero(), @@ -654,9 +708,9 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a // and/or records (e.g. { a: { b: { c: True } } }), // so we need to do this recursively on the field type. let field_var = *field.as_inner(); - let field_content = env.subs.get_without_compacting(field_var).content; + let field_content = env.subs.get_content_without_compacting(field_var); let loc_expr = Located { - value: bool_to_ast(env, value, &field_content), + value: bool_to_ast(env, value, field_content), region: Region::zero(), }; @@ -674,10 +728,10 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a } } FlatType::TagUnion(tags, _) if tags.len() == 1 => { - let (tag_name, payload_vars) = tags.iter().next().unwrap(); + let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); let loc_tag_expr = { - let tag_name = &tag_name.as_string(env.interns, env.home); + let tag_name = &tag_name.as_ident_str(env.interns, env.home); let tag_expr = if tag_name.starts_with('@') { Expr::PrivateTag(arena.alloc_str(tag_name)) } else { @@ -696,10 +750,10 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a debug_assert_eq!(payload_vars.len(), 1); let var = *payload_vars.iter().next().unwrap(); - let content = env.subs.get_without_compacting(var).content; + let content = env.subs.get_content_without_compacting(var); let loc_payload = &*arena.alloc(Located { - value: bool_to_ast(env, value, &content), + value: bool_to_ast(env, value, content), region: Region::zero(), }); @@ -709,20 +763,19 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a Expr::Apply(loc_tag_expr, payload, CalledVia::Space) } FlatType::TagUnion(tags, _) if tags.len() == 2 => { - let mut tags_iter = tags.iter(); - let (tag_name_1, payload_vars_1) = tags_iter.next().unwrap(); - let (tag_name_2, payload_vars_2) = tags_iter.next().unwrap(); + let (tag_name_1, payload_vars_1, tag_name_2, payload_vars_2) = + unpack_two_element_tag_union(env.subs, *tags); debug_assert!(payload_vars_1.is_empty()); debug_assert!(payload_vars_2.is_empty()); let tag_name = if value { max_by_key(tag_name_1, tag_name_2, |n| { - n.as_string(env.interns, env.home) + n.as_ident_str(env.interns, env.home) }) } else { min_by_key(tag_name_1, tag_name_2, |n| { - n.as_string(env.interns, env.home) + n.as_ident_str(env.interns, env.home) }) }; @@ -734,9 +787,9 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a } } Alias(_, _, var) => { - let content = env.subs.get_without_compacting(*var).content; + let content = env.subs.get_content_without_compacting(*var); - bool_to_ast(env, value, &content) + bool_to_ast(env, value, content) } other => { unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); @@ -755,7 +808,11 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a> FlatType::Record(fields, _) => { debug_assert_eq!(fields.len(), 1); - let (label, field) = fields.iter().next().unwrap(); + let (label, field) = fields + .sorted_iterator(env.subs, Variable::EMPTY_RECORD) + .next() + .unwrap(); + let loc_label = Located { value: &*arena.alloc_str(label.as_str()), region: Region::zero(), @@ -766,9 +823,9 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a> // and/or records (e.g. { a: { b: { c: True } } }), // so we need to do this recursively on the field type. let field_var = *field.as_inner(); - let field_content = env.subs.get_without_compacting(field_var).content; + let field_content = env.subs.get_content_without_compacting(field_var); let loc_expr = Located { - value: byte_to_ast(env, value, &field_content), + value: byte_to_ast(env, value, field_content), region: Region::zero(), }; @@ -786,10 +843,10 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a> } } FlatType::TagUnion(tags, _) if tags.len() == 1 => { - let (tag_name, payload_vars) = tags.iter().next().unwrap(); + let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); let loc_tag_expr = { - let tag_name = &tag_name.as_string(env.interns, env.home); + let tag_name = &tag_name.as_ident_str(env.interns, env.home); let tag_expr = if tag_name.starts_with('@') { Expr::PrivateTag(arena.alloc_str(tag_name)) } else { @@ -808,10 +865,10 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a> debug_assert_eq!(payload_vars.len(), 1); let var = *payload_vars.iter().next().unwrap(); - let content = env.subs.get_without_compacting(var).content; + let content = env.subs.get_content_without_compacting(var); let loc_payload = &*arena.alloc(Located { - value: byte_to_ast(env, value, &content), + value: byte_to_ast(env, value, content), region: Region::zero(), }); @@ -824,8 +881,10 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a> // anything with fewer tags is not a byte debug_assert!(tags.len() > 2); - let tags_vec: std::vec::Vec<(TagName, std::vec::Vec)> = - tags.iter().map(|(a, b)| (a.clone(), b.clone())).collect(); + let tags_vec: std::vec::Vec<(TagName, std::vec::Vec)> = tags + .unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION) + .map(|(a, b)| (a.clone(), b.to_vec())) + .collect(); let union_variant = union_sorted_tags_help(env.arena, tags_vec, None, env.subs); @@ -845,9 +904,9 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a> } } Alias(_, _, var) => { - let content = env.subs.get_without_compacting(*var).content; + let content = env.subs.get_content_without_compacting(*var); - byte_to_ast(env, value, &content) + byte_to_ast(env, value, content) } other => { unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); @@ -871,7 +930,11 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E // Its type signature will tell us that. debug_assert_eq!(fields.len(), 1); - let (label, field) = fields.iter().next().unwrap(); + let (label, field) = fields + .sorted_iterator(env.subs, Variable::EMPTY_RECORD) + .next() + .unwrap(); + let loc_label = Located { value: &*arena.alloc_str(label.as_str()), region: Region::zero(), @@ -882,9 +945,9 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E // and/or records (e.g. { a: { b: { c: 5 } } }), // so we need to do this recursively on the field type. let field_var = *field.as_inner(); - let field_content = env.subs.get_without_compacting(field_var).content; + let field_content = env.subs.get_content_without_compacting(field_var); let loc_expr = Located { - value: num_to_ast(env, num_expr, &field_content), + value: num_to_ast(env, num_expr, field_content), region: Region::zero(), }; @@ -904,7 +967,7 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E // This was a single-tag union that got unwrapped at runtime. debug_assert_eq!(tags.len(), 1); - let (tag_name, payload_vars) = tags.iter().next().unwrap(); + let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); // If this tag union represents a number, skip right to // returning tis as an Expr::Num @@ -913,7 +976,7 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E } let loc_tag_expr = { - let tag_name = &tag_name.as_string(env.interns, env.home); + let tag_name = &tag_name.as_ident_str(env.interns, env.home); let tag_expr = if tag_name.starts_with('@') { Expr::PrivateTag(arena.alloc_str(tag_name)) } else { @@ -932,10 +995,10 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E debug_assert_eq!(payload_vars.len(), 1); let var = *payload_vars.iter().next().unwrap(); - let content = env.subs.get_without_compacting(var).content; + let content = env.subs.get_content_without_compacting(var); let loc_payload = &*arena.alloc(Located { - value: num_to_ast(env, num_expr, &content), + value: num_to_ast(env, num_expr, content), region: Region::zero(), }); @@ -950,9 +1013,9 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E } } Alias(_, _, var) => { - let content = env.subs.get_without_compacting(*var).content; + let content = env.subs.get_content_without_compacting(*var); - num_to_ast(env, num_expr, &content) + num_to_ast(env, num_expr, content) } other => { panic!("Unexpected FlatType {:?} in num_to_ast", other); diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs index 797fdba999..76843c9d2d 100644 --- a/cli/src/repl/gen.rs +++ b/cli/src/repl/gen.rs @@ -135,10 +135,6 @@ pub fn gen_and_eval<'a>( &context, "", )); - // Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no - // platform to provide them. - add_default_roc_externs(&context, module, &builder, ptr_bytes); - // mark our zig-defined builtins as internal for function in FunctionIterator::from_module(module) { let name = function.get_name().to_str().unwrap(); @@ -154,8 +150,8 @@ pub fn gen_and_eval<'a>( // pretty-print the expr type string for later. name_all_type_vars(main_fn_var, &mut subs); - let content = subs.get(main_fn_var).content; - let expr_type_str = content_to_string(content.clone(), &subs, home, &interns); + let content = subs.get_content_without_compacting(main_fn_var); + let expr_type_str = content_to_string(content, &subs, home, &interns); let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { Some(layout) => *layout, @@ -183,11 +179,15 @@ pub fn gen_and_eval<'a>( interns, module, ptr_bytes, - leak: false, + is_gen_test: false, // important! we don't want any procedures to get the C calling convention exposed_to_host: MutSet::default(), }; + // Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no + // platform to provide them. + add_default_roc_externs(&env); + let (main_fn_name, main_fn) = roc_gen_llvm::llvm::build::build_procedures_return_main( &env, opt_level, @@ -219,7 +219,7 @@ pub fn gen_and_eval<'a>( ); } - let lib = module_to_dylib(&env.module, &target, opt_level) + let lib = module_to_dylib(env.module, &target, opt_level) .expect("Error loading compiled dylib for test"); let res_answer = unsafe { eval::jit_to_ast( @@ -227,7 +227,7 @@ pub fn gen_and_eval<'a>( lib, main_fn_name, main_fn_layout, - &content, + content, &env.interns, home, &subs, diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 19c50a08f8..0fb00a9e48 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -1,8 +1,7 @@ -// #[macro_use] +#[macro_use] extern crate pretty_assertions; extern crate bumpalo; -extern crate inlinable_string; extern crate roc_collections; extern crate roc_load; extern crate roc_module; @@ -10,12 +9,15 @@ extern crate roc_module; #[cfg(test)] mod cli_run { use cli_utils::helpers::{ - example_file, extract_valgrind_errors, fixture_file, run_cmd, run_roc, run_with_valgrind, - ValgrindError, ValgrindErrorXWhat, + example_file, examples_dir, extract_valgrind_errors, fixture_file, run_cmd, run_roc, + run_with_valgrind, ValgrindError, ValgrindErrorXWhat, }; use serial_test::serial; use std::path::Path; + #[cfg(not(debug_assertions))] + use roc_collections::all::MutMap; + #[cfg(not(target_os = "macos"))] const ALLOW_VALGRIND: bool = true; @@ -25,26 +27,18 @@ mod cli_run { #[cfg(target_os = "macos")] const ALLOW_VALGRIND: bool = false; - fn check_output( - file: &Path, - executable_filename: &str, - flags: &[&str], - expected_ending: &str, + #[derive(Debug, PartialEq, Eq)] + struct Example<'a> { + filename: &'a str, + executable_filename: &'a str, + stdin: &'a [&'a str], + expected_ending: &'a str, use_valgrind: bool, - ) { - check_output_with_stdin( - file, - "", - executable_filename, - flags, - expected_ending, - use_valgrind, - ) } fn check_output_with_stdin( file: &Path, - stdin_str: &str, + stdin: &[&str], executable_filename: &str, flags: &[&str], expected_ending: &str, @@ -59,7 +53,7 @@ mod cli_run { let out = if use_valgrind && ALLOW_VALGRIND { let (valgrind_out, raw_xml) = run_with_valgrind( - stdin_str, + stdin, &[file.with_file_name(executable_filename).to_str().unwrap()], ); @@ -101,7 +95,7 @@ mod cli_run { } else { run_cmd( file.with_file_name(executable_filename).to_str().unwrap(), - stdin_str, + stdin, &[], ) }; @@ -114,182 +108,344 @@ mod cli_run { assert!(out.status.success()); } - #[test] - #[serial(hello_world)] - fn run_hello_world() { - check_output( - &example_file("hello-world", "Hello.roc"), - "hello-world", - &[], - "Hello, World!\n", - true, - ); + /// This macro does two things. + /// + /// First, it generates and runs a separate test for each of the given + /// Example expressions. Each of these should test a particular .roc file + /// in the examples/ directory. + /// + /// Second, it generates an extra test which (non-recursively) traverses the + /// examples/ directory and verifies that each of the .roc files in there + /// has had a corresponding test generated in the previous step. This test + /// will fail if we ever add a new .roc file to examples/ and forget to + /// add a test for it here! + macro_rules! examples { + ($($test_name:ident:$name:expr => $example:expr,)+) => { + $( + #[test] + fn $test_name() { + let dir_name = $name; + let example = $example; + let file_name = example_file(dir_name, example.filename); + + // Check with and without optimizations + check_output_with_stdin( + &file_name, + example.stdin, + example.executable_filename, + &[], + example.expected_ending, + example.use_valgrind, + ); + + check_output_with_stdin( + &file_name, + example.stdin, + example.executable_filename, + &["--optimize"], + example.expected_ending, + example.use_valgrind, + ); + } + )* + + #[test] + #[cfg(not(debug_assertions))] + fn all_examples_have_tests() { + let mut all_examples: MutMap<&str, Example<'_>> = MutMap::default(); + + $( + all_examples.insert($name, $example); + )* + + check_for_tests("../examples", &mut all_examples); + } + } } - #[test] - #[serial(hello_world)] - fn run_hello_world_optimized() { - check_output( - &example_file("hello-world", "Hello.roc"), - "hello-world", - &[], - "Hello, World!\n", - true, - ); + // examples! macro format: + // + // "name-of-subdirectory-inside-examples-dir" => [ + // test_name_1: Example { + // ... + // }, + // test_name_2: Example { + // ... + // }, + // ] + examples! { + hello_world:"hello-world" => Example { + filename: "Hello.roc", + executable_filename: "hello-world", + stdin: &[], + expected_ending:"Hello, World!\n", + use_valgrind: true, + }, + hello_zig:"hello-zig" => Example { + filename: "Hello.roc", + executable_filename: "hello-world", + stdin: &[], + expected_ending:"Hello, World!\n", + use_valgrind: true, + }, + hello_rust:"hello-rust" => Example { + filename: "Hello.roc", + executable_filename: "hello-world", + stdin: &[], + expected_ending:"Hello, World!\n", + use_valgrind: true, + }, + quicksort:"quicksort" => Example { + filename: "Quicksort.roc", + executable_filename: "quicksort", + stdin: &[], + expected_ending: "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", + use_valgrind: true, + }, + // shared_quicksort:"shared-quicksort" => Example { + // filename: "Quicksort.roc", + // executable_filename: "quicksort", + // stdin: &[], + // expected_ending: "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", + // use_valgrind: true, + // }, + effect:"effect" => Example { + filename: "Main.roc", + executable_filename: "effect-example", + stdin: &["hi there!"], + expected_ending: "hi there!\n", + use_valgrind: true, + }, + // tea:"tea" => Example { + // filename: "Main.roc", + // executable_filename: "tea-example", + // stdin: &[], + // expected_ending: "", + // use_valgrind: true, + // }, + // cli:"cli" => Example { + // filename: "Echo.roc", + // executable_filename: "echo", + // stdin: &["Giovanni\n", "Giorgio\n"], + // expected_ending: "Giovanni Giorgio!\n", + // use_valgrind: true, + // }, + // custom_malloc:"custom-malloc" => Example { + // filename: "Main.roc", + // executable_filename: "custom-malloc-example", + // stdin: &[], + // expected_ending: "ms!\nThe list was small!\n", + // use_valgrind: true, + // }, + // task:"task" => Example { + // filename: "Main.roc", + // executable_filename: "task-example", + // stdin: &[], + // expected_ending: "successfully wrote to file\n", + // use_valgrind: true, + // }, } - #[test] - #[serial(quicksort)] - fn run_quicksort_not_optimized() { - check_output( - &example_file("quicksort", "Quicksort.roc"), - "quicksort", - &[], - "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", - true, - ); + macro_rules! benchmarks { + ($($test_name:ident => $benchmark:expr,)+) => { + $( + #[test] + #[cfg_attr(not(debug_assertions), serial(benchmark))] + fn $test_name() { + let benchmark = $benchmark; + let file_name = examples_dir("benchmarks").join(benchmark.filename); + + // TODO fix QuicksortApp and RBTreeCk and then remove this! + match benchmark.filename { + "QuicksortApp.roc" | "RBTreeCk.roc" => { + eprintln!("WARNING: skipping testing benchmark {} because the test is broken right now!", benchmark.filename); + return; + } + _ => {} + } + + // Check with and without optimizations + check_output_with_stdin( + &file_name, + benchmark.stdin, + benchmark.executable_filename, + &[], + benchmark.expected_ending, + benchmark.use_valgrind, + ); + + check_output_with_stdin( + &file_name, + benchmark.stdin, + benchmark.executable_filename, + &["--optimize"], + benchmark.expected_ending, + benchmark.use_valgrind, + ); + } + )* + + #[test] + #[cfg(not(debug_assertions))] + fn all_benchmarks_have_tests() { + let mut all_benchmarks: MutMap<&str, Example<'_>> = MutMap::default(); + + $( + let benchmark = $benchmark; + + all_benchmarks.insert(benchmark.filename, benchmark); + )* + + check_for_benchmarks("../examples/benchmarks", &mut all_benchmarks); + } + } } - #[test] - #[serial(quicksort)] - fn run_quicksort_optimized() { - check_output( - &example_file("quicksort", "Quicksort.roc"), - "quicksort", - &["--optimize"], - "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", - true, - ); + benchmarks! { + nqueens => Example { + filename: "NQueens.roc", + executable_filename: "nqueens", + stdin: &["6"], + expected_ending: "4\n", + use_valgrind: true, + }, + cfold => Example { + filename: "CFold.roc", + executable_filename: "cfold", + stdin: &["3"], + expected_ending: "11 & 11\n", + use_valgrind: true, + }, + deriv => Example { + filename: "Deriv.roc", + executable_filename: "deriv", + stdin: &["2"], + expected_ending: "1 count: 6\n2 count: 22\n", + use_valgrind: true, + }, + rbtree_ck => Example { + filename: "RBTreeCk.roc", + executable_filename: "rbtree-ck", + stdin: &[], + expected_ending: "Node Black 0 {} Empty Empty\n", + use_valgrind: true, + }, + rbtree_insert => Example { + filename: "RBTreeInsert.roc", + executable_filename: "rbtree-insert", + stdin: &[], + expected_ending: "Node Black 0 {} Empty Empty\n", + use_valgrind: true, + }, + rbtree_del => Example { + filename: "RBTreeDel.roc", + executable_filename: "rbtree-del", + stdin: &["420"], + expected_ending: "30\n", + use_valgrind: true, + }, + astar => Example { + filename: "TestAStar.roc", + executable_filename: "test-astar", + stdin: &[], + expected_ending: "True\n", + use_valgrind: false, + }, + base64 => Example { + filename: "TestBase64.roc", + executable_filename: "test-base64", + stdin: &[], + expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n", + use_valgrind: true, + }, + closure => Example { + filename: "Closure.roc", + executable_filename: "closure", + stdin: &[], + expected_ending: "", + use_valgrind: true, + }, + quicksort_app => Example { + filename: "QuicksortApp.roc", + executable_filename: "quicksortapp", + stdin: &[], + expected_ending: "todo put the correct quicksort answer here", + use_valgrind: true, + }, } - #[test] - #[serial(quicksort)] - fn run_quicksort_optimized_valgrind() { - check_output( - &example_file("quicksort", "Quicksort.roc"), - "quicksort", - &["--optimize"], - "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", - true, - ); + #[cfg(not(debug_assertions))] + fn check_for_tests(examples_dir: &str, all_examples: &mut MutMap<&str, Example<'_>>) { + let entries = std::fs::read_dir(examples_dir).unwrap_or_else(|err| { + panic!( + "Error trying to read {} as an examples directory: {}", + examples_dir, err + ); + }); + + for entry in entries { + let entry = entry.unwrap(); + + if entry.file_type().unwrap().is_dir() { + let example_dir_name = entry.file_name().into_string().unwrap(); + + // We test benchmarks separately + if example_dir_name != "benchmarks" { + all_examples.remove(example_dir_name.as_str()).unwrap_or_else(|| { + panic!("The example directory {}/{} does not have any corresponding tests in cli_run. Please add one, so if it ever stops working, we'll know about it right away!", examples_dir, example_dir_name); + }); + } + } + } + + assert_eq!(all_examples, &mut MutMap::default()); } - #[test] - #[serial(nqueens)] - fn run_nqueens_not_optimized() { - check_output_with_stdin( - &example_file("benchmarks", "NQueens.roc"), - "6", - "nqueens", - &[], - "4\n", - true, - ); - } + #[cfg(not(debug_assertions))] + fn check_for_benchmarks(benchmarks_dir: &str, all_benchmarks: &mut MutMap<&str, Example<'_>>) { + use std::ffi::OsStr; + use std::fs::File; + use std::io::Read; - #[test] - #[serial(cfold)] - fn run_cfold_not_optimized() { - check_output_with_stdin( - &example_file("benchmarks", "CFold.roc"), - "3", - "cfold", - &[], - "11 & 11\n", - true, - ); - } + let entries = std::fs::read_dir(benchmarks_dir).unwrap_or_else(|err| { + panic!( + "Error trying to read {} as a benchmark directory: {}", + benchmarks_dir, err + ); + }); - #[test] - #[serial(deriv)] - fn run_deriv_not_optimized() { - check_output_with_stdin( - &example_file("benchmarks", "Deriv.roc"), - "2", - "deriv", - &[], - "1 count: 6\n2 count: 22\n", - true, - ); - } + for entry in entries { + let entry = entry.unwrap(); + let path = entry.path(); - #[test] - #[serial(deriv)] - fn run_rbtree_insert_not_optimized() { - check_output( - &example_file("benchmarks", "RBTreeInsert.roc"), - "rbtree-insert", - &[], - "Node Black 0 {} Empty Empty\n", - true, - ); - } + if let Some("roc") = path.extension().and_then(OsStr::to_str) { + let benchmark_file_name = entry.file_name().into_string().unwrap(); - #[test] - #[serial(deriv)] - fn run_rbtree_delete_not_optimized() { - check_output_with_stdin( - &example_file("benchmarks", "RBTreeDel.roc"), - "420", - "rbtree-del", - &[], - "30\n", - true, - ); - } + // Verify that this is an app module by reading the first 3 + // bytes of the file. + let buf: &mut [u8] = &mut [0, 0, 0]; + let mut file = File::open(path).unwrap(); - #[test] - #[serial(astar)] - fn run_astar_optimized_1() { - check_output( - &example_file("benchmarks", "TestAStar.roc"), - "test-astar", - &[], - "True\n", - false, - ); - } + file.read_exact(buf).unwrap(); - #[test] - #[serial(base64)] - fn base64() { - check_output( - &example_file("benchmarks", "TestBase64.roc"), - "test-base64", - &[], - "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n", - true, - ); - } + // Only app modules in this directory are considered benchmarks. + if "app".as_bytes() == buf { + all_benchmarks.remove(benchmark_file_name.as_str()).unwrap_or_else(|| { + panic!("The benchmark {}/{} does not have any corresponding tests in cli_run. Please add one, so if it ever stops working, we'll know about it right away!", benchmarks_dir, benchmark_file_name); + }); + } + } + } - #[test] - #[serial(closure)] - fn closure() { - check_output( - &example_file("benchmarks", "Closure.roc"), - "closure", - &[], - "", - true, - ); + assert_eq!(all_benchmarks, &mut MutMap::default()); } - // #[test] - // #[serial(effect)] - // fn run_effect_unoptimized() { - // check_output( - // &example_file("effect", "Main.roc"), - // &[], - // "I am Dep2.str2\n", - // true, - // ); - // } - #[test] #[serial(multi_dep_str)] fn run_multi_dep_str_unoptimized() { - check_output( + check_output_with_stdin( &fixture_file("multi-dep-str", "Main.roc"), + &[], "multi-dep-str", &[], "I am Dep2.str2\n", @@ -300,8 +456,9 @@ mod cli_run { #[test] #[serial(multi_dep_str)] fn run_multi_dep_str_optimized() { - check_output( + check_output_with_stdin( &fixture_file("multi-dep-str", "Main.roc"), + &[], "multi-dep-str", &["--optimize"], "I am Dep2.str2\n", @@ -312,8 +469,9 @@ mod cli_run { #[test] #[serial(multi_dep_thunk)] fn run_multi_dep_thunk_unoptimized() { - check_output( + check_output_with_stdin( &fixture_file("multi-dep-thunk", "Main.roc"), + &[], "multi-dep-thunk", &[], "I am Dep2.value2\n", @@ -324,8 +482,9 @@ mod cli_run { #[test] #[serial(multi_dep_thunk)] fn run_multi_dep_thunk_optimized() { - check_output( + check_output_with_stdin( &fixture_file("multi-dep-thunk", "Main.roc"), + &[], "multi-dep-thunk", &["--optimize"], "I am Dep2.value2\n", diff --git a/cli/tests/fixtures/multi-dep-str/platform/host.zig b/cli/tests/fixtures/multi-dep-str/platform/host.zig index e60688a037..bf16bd9b65 100644 --- a/cli/tests/fixtures/multi-dep-str/platform/host.zig +++ b/cli/tests/fixtures/multi-dep-str/platform/host.zig @@ -40,6 +40,13 @@ export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { free(@alignCast(16, @ptrCast([*]u8, c_ptr))); } +export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { + const stderr = std.io.getStdErr().writer(); + const msg = @ptrCast([*:0]const u8, c_ptr); + stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; + std.process.exit(0); +} + const RocCallResult = extern struct { flag: usize, content: RocStr }; const Unit = extern struct {}; diff --git a/cli/tests/fixtures/multi-dep-thunk/platform/host.zig b/cli/tests/fixtures/multi-dep-thunk/platform/host.zig index e60688a037..bf16bd9b65 100644 --- a/cli/tests/fixtures/multi-dep-thunk/platform/host.zig +++ b/cli/tests/fixtures/multi-dep-thunk/platform/host.zig @@ -40,6 +40,13 @@ export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { free(@alignCast(16, @ptrCast([*]u8, c_ptr))); } +export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { + const stderr = std.io.getStdErr().writer(); + const msg = @ptrCast([*:0]const u8, c_ptr); + stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; + std.process.exit(0); +} + const RocCallResult = extern struct { flag: usize, content: RocStr }; const Unit = extern struct {}; diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index ee1124c905..54af7c5872 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -56,7 +56,7 @@ fn find_zig_str_path() -> PathBuf { } #[cfg(not(target_os = "macos"))] -fn build_zig_host( +pub fn build_zig_host( env_path: &str, env_home: &str, emit_bin: &str, @@ -86,7 +86,7 @@ fn build_zig_host( } #[cfg(target_os = "macos")] -fn build_zig_host( +pub fn build_zig_host( env_path: &str, env_home: &str, emit_bin: &str, @@ -140,7 +140,7 @@ fn build_zig_host( .args(&[ "build-obj", zig_host_src, - &emit_bin, + emit_bin, "--pkg-begin", "str", zig_str_path, @@ -291,38 +291,60 @@ pub fn rebuild_host(host_input_path: &Path) { } } +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 library_path(segments: [&str; N]) -> Option { + let mut guess_path = PathBuf::new(); + for s in segments { + guess_path.push(s); + } + if guess_path.exists() { + Some(guess_path) + } else { + None + } +} + fn link_linux( target: &Triple, output_path: PathBuf, input_paths: &[&str], link_type: LinkType, ) -> io::Result<(Child, PathBuf)> { - let usr_lib_path = Path::new("/usr/lib").to_path_buf(); - let usr_lib_gnu_path = usr_lib_path.join(format!("{}-linux-gnu", target.architecture)); - let lib_gnu_path = Path::new("/lib/").join(format!("{}-linux-gnu", target.architecture)); + let architecture = format!("{}-linux-gnu", target.architecture); - let libcrt_path = if usr_lib_gnu_path.exists() { - &usr_lib_gnu_path - } else { - &usr_lib_path - }; + let libcrt_path = library_path(["/usr", "lib", &architecture]) + .or_else(|| library_path(["/usr", "lib"])) + .or_else(|| library_path([&nixos_path()])) + .unwrap(); let libgcc_name = "libgcc_s.so.1"; - let libgcc_path = if lib_gnu_path.join(libgcc_name).exists() { - lib_gnu_path.join(libgcc_name) - } else if usr_lib_gnu_path.join(libgcc_name).exists() { - usr_lib_gnu_path.join(libgcc_name) - } else { - usr_lib_path.join(libgcc_name) - }; + 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 ld_linux = match target.architecture { - Architecture::X86_64 => "/lib64/ld-linux-x86-64.so.2", - Architecture::Aarch64(_) => "/lib/ld-linux-aarch64.so.1", + 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::Aarch64(_) => library_path(["/lib", "ld-linux-aarch64.so.1"]), _ => panic!( "TODO gracefully handle unsupported linux architecture: {:?}", target.architecture ), }; + let ld_linux = ld_linux.unwrap(); + let ld_linux = ld_linux.to_str().unwrap(); let mut soname; let (base_args, output_path) = match link_type { @@ -333,7 +355,7 @@ fn link_linux( output_path, ), LinkType::Dylib => { - // TODO: do we acually need the version number on this? + // TODO: do we actually need the version number on this? // Do we even need the "-soname" argument? // // See https://software.intel.com/content/www/us/en/develop/articles/create-a-unix-including-linux-shared-library.html @@ -399,9 +421,6 @@ fn link_linux( "-lrt", "-lutil", "-lc_nonshared", - "-lc++", - "-lc++abi", - "-lunwind", libgcc_path.to_str().unwrap(), // Output "-o", @@ -432,7 +451,7 @@ 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 = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib"; + 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 { @@ -466,9 +485,6 @@ fn link_macos( // "-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 - "-lc++", - // "-lc++abi", - // "-lunwind", // TODO will eventually need this, 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 @@ -496,7 +512,7 @@ pub fn module_to_dylib( app_o_file.set_file_name("app.o"); - // Emit the .o file using position-indepedent code (PIC) - needed for dylibs + // Emit the .o file using position-independent code (PIC) - needed for dylibs let reloc = RelocMode::PIC; let model = CodeModel::Default; let target_machine = diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 82d7261950..fc6af6b3e5 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -16,6 +16,10 @@ pub struct CodeGenTiming { pub emit_o_file: Duration, } +// TODO: If modules besides this one start needing to know which version of +// llvm we're using, consider moving me somewhere else. +const LLVM_VERSION: &str = "12"; + // TODO how should imported modules factor into this? What if those use builtins too? // TODO this should probably use more helper functions // TODO make this polymorphic in the llvm functions so it can be reused for another backend. @@ -116,9 +120,10 @@ pub fn gen_from_mono_module( } if name.starts_with("roc_builtins.dict") - || name.starts_with("dict.RocDict") || name.starts_with("roc_builtins.list") + || name.starts_with("roc_builtins.dec") || name.starts_with("list.RocList") + || name.starts_with("dict.RocDict") { function.add_attribute(AttributeLoc::Function, enum_attr); } @@ -131,7 +136,7 @@ pub fn gen_from_mono_module( // Compile and add all the Procs before adding main let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let env = roc_gen_llvm::llvm::build::Env { - arena: &arena, + arena, builder: &builder, dibuilder: &dibuilder, compile_unit: &compile_unit, @@ -139,7 +144,9 @@ pub fn gen_from_mono_module( interns: loaded.interns, module, ptr_bytes, - leak: false, + // in gen_tests, the compiler provides roc_panic + // and sets up the setjump/longjump exception handling + is_gen_test: false, exposed_to_host: loaded.exposed_to_host.keys().copied().collect(), }; @@ -153,6 +160,9 @@ pub fn gen_from_mono_module( env.dibuilder.finalize(); + // we don't use the debug info, and it causes weird errors. + module.strip_debug_info(); + // Uncomment this to see the module's optimized LLVM instruction output: // env.module.print_to_stderr(); @@ -194,7 +204,6 @@ pub fn gen_from_mono_module( // run the debugir https://github.com/vaivaswatha/debugir tool match Command::new("debugir") - .env_clear() .args(&["-instnamer", app_ll_file.to_str().unwrap()]) .output() { @@ -212,7 +221,6 @@ pub fn gen_from_mono_module( // assemble the .ll into a .bc let _ = Command::new("llvm-as") - .env_clear() .args(&[ app_ll_dbg_file.to_str().unwrap(), "-o", @@ -221,18 +229,26 @@ pub fn gen_from_mono_module( .output() .unwrap(); + let llc_args = &[ + "-filetype=obj", + app_bc_file.to_str().unwrap(), + "-o", + app_o_file.to_str().unwrap(), + ]; + // write the .o file. Note that this builds the .o for the local machine, // and ignores the `target_machine` entirely. - let _ = Command::new("llc-12") - .env_clear() - .args(&[ - "-filetype=obj", - app_bc_file.to_str().unwrap(), - "-o", - app_o_file.to_str().unwrap(), - ]) - .output() - .unwrap(); + // + // different systems name this executable differently, so we shotgun for + // the most common ones and then give up. + let _: Result = + Command::new(format!("llc-{}", LLVM_VERSION)) + .args(llc_args) + .output() + .or_else(|_| Command::new("llc").args(llc_args).output()) + .map_err(|_| { + panic!("We couldn't find llc-{} on your machine!", LLVM_VERSION); + }); } else { // Emit the .o file @@ -242,7 +258,7 @@ pub fn gen_from_mono_module( target::target_machine(&target, convert_opt_level(opt_level), reloc, model).unwrap(); target_machine - .write_to_file(&env.module, FileType::Object, &app_o_file) + .write_to_file(env.module, FileType::Object, app_o_file) .expect("Writing .o file failed"); } diff --git a/compiler/builtins/bitcode/build.zig b/compiler/builtins/bitcode/build.zig index e9835e0db5..e90c8ab7fe 100644 --- a/compiler/builtins/bitcode/build.zig +++ b/compiler/builtins/bitcode/build.zig @@ -27,7 +27,6 @@ pub fn build(b: *Builder) void { llvm_obj.strip = true; llvm_obj.emit_llvm_ir = true; llvm_obj.emit_bin = false; - llvm_obj.bundle_compiler_rt = true; const ir = b.step("ir", "Build LLVM ir"); ir.dependOn(&llvm_obj.step); diff --git a/compiler/builtins/bitcode/src/dec.zig b/compiler/builtins/bitcode/src/dec.zig index 011e37cac1..aad37e91a5 100644 --- a/compiler/builtins/bitcode/src/dec.zig +++ b/compiler/builtins/bitcode/src/dec.zig @@ -1,16 +1,19 @@ const std = @import("std"); const str = @import("str.zig"); +const utils = @import("utils.zig"); const math = std.math; +const always_inline = std.builtin.CallOptions.Modifier.always_inline; const RocStr = str.RocStr; +const WithOverflow = utils.WithOverflow; -pub const RocDec = struct { +pub const RocDec = extern struct { num: i128, pub const decimal_places: u5 = 18; pub const whole_number_places: u5 = 21; const max_digits: u6 = 39; - const leading_zeros: [17]u8 = "00000000000000000".*; + const max_str_length: u6 = max_digits + 2; // + 2 here to account for the sign & decimal dot pub const min: RocDec = .{ .num = math.minInt(i128) }; pub const max: RocDec = .{ .num = math.maxInt(i128) }; @@ -22,6 +25,23 @@ pub const RocDec = struct { return .{ .num = num * one_point_zero_i128 }; } + // TODO: There's got to be a better way to do this other than converting to Str + pub fn fromF64(num: f64) ?RocDec { + var digit_bytes: [19]u8 = undefined; // 19 = max f64 digits + '.' + '-' + + var fbs = std.io.fixedBufferStream(digit_bytes[0..]); + std.fmt.formatFloatDecimal(num, .{}, fbs.writer()) catch + return null; + + var dec = RocDec.fromStr(RocStr.init(&digit_bytes, fbs.pos)); + + if (dec) |d| { + return d; + } else { + return null; + } + } + pub fn fromStr(roc_str: RocStr) ?RocDec { if (roc_str.isEmpty()) { return null; @@ -57,7 +77,7 @@ pub const RocDec = struct { var after_str_len = (length - 1) - pi; if (after_str_len > decimal_places) { - std.debug.panic("TODO runtime exception for too many decimal places!", .{}); + @panic("TODO runtime exception for too many decimal places!"); } var diff_decimal_places = decimal_places - after_str_len; @@ -74,37 +94,38 @@ pub const RocDec = struct { var result: i128 = undefined; var overflowed = @mulWithOverflow(i128, before, one_point_zero_i128, &result); if (overflowed) { - std.debug.panic("TODO runtime exception for overflow!", .{}); + @panic("TODO runtime exception for overflow!"); } before_val_i128 = result; } - var dec: ?RocDec = null; - if (before_val_i128) |before| { - if (after_val_i128) |after| { - var result: i128 = undefined; - var overflowed = @addWithOverflow(i128, before, after, &result); - if (overflowed) { - std.debug.panic("TODO runtime exception for overflow!", .{}); + const dec: RocDec = blk: { + if (before_val_i128) |before| { + if (after_val_i128) |after| { + var result: i128 = undefined; + var overflowed = @addWithOverflow(i128, before, after, &result); + if (overflowed) { + @panic("TODO runtime exception for overflow!"); + } + break :blk .{ .num = result }; + } else { + break :blk .{ .num = before }; } - dec = .{ .num = result }; + } else if (after_val_i128) |after| { + break :blk .{ .num = after }; } else { - dec = .{ .num = before }; + return null; } - } else if (after_val_i128) |after| { - dec = .{ .num = after }; - } + }; - if (dec) |d| { - if (is_negative) { - dec = d.negate(); - } + if (is_negative) { + return dec.negate(); + } else { + return dec; } - - return dec; } - fn isDigit(c: u8) bool { + inline fn isDigit(c: u8) bool { return (c -% 48) <= 9; } @@ -114,80 +135,84 @@ pub const RocDec = struct { return RocStr.init("0.0", 3); } - // Check if this Dec is negative, and if so convert to positive - // We will handle adding the '-' later - const is_negative = self.num < 0; - const num = if (is_negative) std.math.negate(self.num) catch { - std.debug.panic("TODO runtime exception failing to negate", .{}); - } else self.num; + const num = self.num; + const is_negative = num < 0; - // Format the backing i128 into an array of digits (u8s) - var digit_bytes: [max_digits + 1]u8 = undefined; - var num_digits = std.fmt.formatIntBuf(digit_bytes[0..], num, 10, false, .{}); + // Format the backing i128 into an array of digit (ascii) characters (u8s) + var digit_bytes_storage: [max_digits + 1]u8 = undefined; + var num_digits = std.fmt.formatIntBuf(digit_bytes_storage[0..], num, 10, false, .{}); + var digit_bytes: [*]u8 = digit_bytes_storage[0..]; + + // space where we assemble all the characters that make up the final string + var str_bytes: [max_str_length]u8 = undefined; + var position: usize = 0; + + // if negative, the first character is a negating minus + if (is_negative) { + str_bytes[position] = '-'; + position += 1; + + // but also, we have one fewer digit than we have characters + num_digits -= 1; + + // and we drop the minus to make later arithmetic correct + digit_bytes += 1; + } // Get the slice for before the decimal point - var before_digits_slice: []const u8 = undefined; var before_digits_offset: usize = 0; - var before_digits_adjust: u6 = 0; if (num_digits > decimal_places) { + // we have more digits than fit after the decimal point, + // so we must have digits before the decimal point before_digits_offset = num_digits - decimal_places; - before_digits_slice = digit_bytes[0..before_digits_offset]; - } else { - before_digits_adjust = @intCast(u6, math.absInt(@intCast(i7, num_digits) - decimal_places) catch { - std.debug.panic("TODO runtime exception for overflow when getting abs", .{}); - }); - before_digits_slice = "0"; - } - // Figure out how many trailing zeros there are - // I tried to use https://ziglang.org/documentation/0.8.0/#ctz and it mostly worked, - // but was giving seemingly incorrect values for certain numbers. So instead we use - // a while loop and figure it out that way. - // - // const trailing_zeros = @ctz(u6, num); - // - var trailing_zeros: u6 = 0; - var index = decimal_places - 1 - before_digits_adjust; - var is_consecutive_zero = true; - while (index != 0) { - var digit = digit_bytes[before_digits_offset + index]; - if (digit == '0' and is_consecutive_zero) { - trailing_zeros += 1; - } else { - is_consecutive_zero = false; + for (digit_bytes[0..before_digits_offset]) |c| { + str_bytes[position] = c; + position += 1; } - index -= 1; - } - - // Figure out if we need to prepend any zeros to the after decimal point - // For example, for the number 0.000123 we need to prepend 3 zeros after the decimal point - // This will only be needed for numbers less 0.01, otherwise after_digits_slice will handle this - const after_zeros_num = if (num_digits < decimal_places) decimal_places - num_digits else 0; - const after_zeros_slice: []const u8 = leading_zeros[0..after_zeros_num]; - - // Get the slice for after the decimal point - var after_digits_slice: []const u8 = undefined; - if ((num_digits - before_digits_offset) == trailing_zeros) { - after_digits_slice = "0"; } else { - after_digits_slice = digit_bytes[before_digits_offset .. num_digits - trailing_zeros]; + // otherwise there are no actual digits before the decimal point + // but we format it with a '0' + str_bytes[position] = '0'; + position += 1; } - // Get the slice for the sign - const sign_slice: []const u8 = if (is_negative) "-" else leading_zeros[0..0]; + // we've done everything before the decimal point, so now we can put the decimal point in + str_bytes[position] = '.'; + position += 1; - // Hardcode adding a `1` for the '.' character - const str_len: usize = sign_slice.len + before_digits_slice.len + 1 + after_zeros_slice.len + after_digits_slice.len; + const trailing_zeros: u6 = count_trailing_zeros_base10(num); + if (trailing_zeros == decimal_places) { + // add just a single zero if all decimal digits are zero + str_bytes[position] = '0'; + position += 1; + } else { + // Figure out if we need to prepend any zeros to the after decimal point + // For example, for the number 0.000123 we need to prepend 3 zeros after the decimal point + const after_zeros_num = if (num_digits < decimal_places) decimal_places - num_digits else 0; - // Join the slices together - // We do `max_digits + 2` here because we need to account for a possible sign ('-') and the dot ('.'). - // Ideally, we'd use str_len here - var str_bytes: [max_digits + 2]u8 = undefined; - _ = std.fmt.bufPrint(str_bytes[0..str_len], "{s}{s}.{s}{s}", .{ sign_slice, before_digits_slice, after_zeros_slice, after_digits_slice }) catch { - std.debug.panic("TODO runtime exception failing to print slices", .{}); - }; + var i: usize = 0; + while (i < after_zeros_num) : (i += 1) { + str_bytes[position] = '0'; + position += 1; + } - return RocStr.init(&str_bytes, str_len); + // otherwise append the decimal digits except the trailing zeros + for (digit_bytes[before_digits_offset .. num_digits - trailing_zeros]) |c| { + str_bytes[position] = c; + position += 1; + } + } + + return RocStr.init(&str_bytes, position); + } + + pub fn eq(self: RocDec, other: RocDec) bool { + return self.num == other.num; + } + + pub fn neq(self: RocDec, other: RocDec) bool { + return self.num != other.num; } pub fn negate(self: RocDec) ?RocDec { @@ -195,29 +220,41 @@ pub const RocDec = struct { return if (negated) |n| .{ .num = n } else null; } - pub fn add(self: RocDec, other: RocDec) RocDec { + pub fn addWithOverflow(self: RocDec, other: RocDec) WithOverflow(RocDec) { var answer: i128 = undefined; const overflowed = @addWithOverflow(i128, self.num, other.num, &answer); - if (!overflowed) { - return RocDec{ .num = answer }; + return .{ .value = RocDec{ .num = answer }, .has_overflowed = overflowed }; + } + + pub fn add(self: RocDec, other: RocDec) RocDec { + const answer = RocDec.addWithOverflow(self, other); + + if (answer.has_overflowed) { + @panic("TODO runtime exception for overflow!"); } else { - std.debug.panic("TODO runtime exception for overflow!", .{}); + return answer.value; } } - pub fn sub(self: RocDec, other: RocDec) RocDec { + pub fn subWithOverflow(self: RocDec, other: RocDec) WithOverflow(RocDec) { var answer: i128 = undefined; const overflowed = @subWithOverflow(i128, self.num, other.num, &answer); - if (!overflowed) { - return RocDec{ .num = answer }; + return .{ .value = RocDec{ .num = answer }, .has_overflowed = overflowed }; + } + + pub fn sub(self: RocDec, other: RocDec) RocDec { + const answer = RocDec.subWithOverflow(self, other); + + if (answer.has_overflowed) { + @panic("TODO runtime exception for overflow!"); } else { - std.debug.panic("TODO runtime exception for overflow!", .{}); + return answer.value; } } - pub fn mul(self: RocDec, other: RocDec) RocDec { + pub fn mulWithOverflow(self: RocDec, other: RocDec) WithOverflow(RocDec) { const self_i128 = self.num; const other_i128 = other.num; // const answer = 0; //self_i256 * other_i256; @@ -226,30 +263,40 @@ pub const RocDec = struct { const self_u128 = @intCast(u128, math.absInt(self_i128) catch { if (other_i128 == 0) { - return .{ .num = 0 }; + return .{ .value = RocDec{ .num = 0 }, .has_overflowed = false }; } else if (other_i128 == RocDec.one_point_zero.num) { - return self; + return .{ .value = self, .has_overflowed = false }; } else { - std.debug.panic("TODO runtime exception for overflow!", .{}); + return .{ .value = undefined, .has_overflowed = true }; } }); const other_u128 = @intCast(u128, math.absInt(other_i128) catch { if (self_i128 == 0) { - return .{ .num = 0 }; + return .{ .value = RocDec{ .num = 0 }, .has_overflowed = false }; } else if (self_i128 == RocDec.one_point_zero.num) { - return other; + return .{ .value = other, .has_overflowed = false }; } else { - std.debug.panic("TODO runtime exception for overflow!", .{}); + return .{ .value = undefined, .has_overflowed = true }; } }); const unsigned_answer: i128 = mul_and_decimalize(self_u128, other_u128); if (is_answer_negative) { - return .{ .num = -unsigned_answer }; + return .{ .value = RocDec{ .num = -unsigned_answer }, .has_overflowed = false }; } else { - return .{ .num = unsigned_answer }; + return .{ .value = RocDec{ .num = unsigned_answer }, .has_overflowed = false }; + } + } + + pub fn mul(self: RocDec, other: RocDec) RocDec { + const answer = RocDec.mulWithOverflow(self, other); + + if (answer.has_overflowed) { + @panic("TODO runtime exception for overflow!"); + } else { + return answer.value; } } @@ -264,7 +311,9 @@ pub const RocDec = struct { // (n / 0) is an error if (denominator_i128 == 0) { - std.debug.panic("TODO runtime exception for divide by 0!", .{}); + // The compiler frontend does the `denominator == 0` check for us, + // therefore this case is unreachable from roc user code + unreachable; } // If they're both negative, or if neither is negative, the final answer @@ -292,7 +341,7 @@ pub const RocDec = struct { if (denominator_i128 == one_point_zero_i128) { return self; } else { - std.debug.panic("TODO runtime exception for overflow when dividing!", .{}); + @panic("TODO runtime exception for overflow when dividing!"); } }; const numerator_u128 = @intCast(u128, numerator_abs_i128); @@ -305,7 +354,7 @@ pub const RocDec = struct { if (numerator_i128 == one_point_zero_i128) { return other; } else { - std.debug.panic("TODO runtime exception for overflow when dividing!", .{}); + @panic("TODO runtime exception for overflow when dividing!"); } }; const denominator_u128 = @intCast(u128, denominator_abs_i128); @@ -317,13 +366,35 @@ pub const RocDec = struct { if (answer.hi == 0 and answer.lo <= math.maxInt(i128)) { unsigned_answer = @intCast(i128, answer.lo); } else { - std.debug.panic("TODO runtime exception for overflow when dividing!", .{}); + @panic("TODO runtime exception for overflow when dividing!"); } return RocDec{ .num = if (is_answer_negative) -unsigned_answer else unsigned_answer }; } }; +// A number has `k` trailling zeros if `10^k` divides into it cleanly +inline fn count_trailing_zeros_base10(input: i128) u6 { + if (input == 0) { + // this should not happen in practice + return 0; + } + + var count: u6 = 0; + var k: i128 = 1; + + while (true) { + if (@mod(input, std.math.pow(i128, 10, k)) == 0) { + count += 1; + k += 1; + } else { + break; + } + } + + return count; +} + const U256 = struct { hi: u128, lo: u128, @@ -437,7 +508,7 @@ fn mul_and_decimalize(a: u128, b: u128) i128 { overflowed = overflowed or @addWithOverflow(u128, d, c_carry4, &d); if (overflowed) { - std.debug.panic("TODO runtime exception for overflow!", .{}); + @panic("TODO runtime exception for overflow!"); } // Final 512bit value is d, c, b, a @@ -652,6 +723,11 @@ test "fromU64" { try expectEqual(RocDec{ .num = 25000000000000000000 }, dec); } +test "fromF64" { + var dec = RocDec.fromF64(25.5); + try expectEqual(RocDec{ .num = 25500000000000000000 }, dec.?); +} + test "fromStr: empty" { var roc_str = RocStr.init("", 0); var dec = RocDec.fromStr(roc_str); @@ -867,6 +943,26 @@ test "toStr: 12345678912345678912.111111111111111111 (max number of digits)" { try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice()); } +test "toStr: std.math.maxInt" { + var dec: RocDec = .{ .num = std.math.maxInt(i128) }; + var res_roc_str = dec.toStr(); + errdefer res_roc_str.?.deinit(); + defer res_roc_str.?.deinit(); + + const res_slice: []const u8 = "170141183460469231731.687303715884105727"[0..]; + try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice()); +} + +test "toStr: std.math.minInt" { + var dec: RocDec = .{ .num = std.math.minInt(i128) }; + var res_roc_str = dec.toStr(); + errdefer res_roc_str.?.deinit(); + defer res_roc_str.?.deinit(); + + const res_slice: []const u8 = "-170141183460469231731.687303715884105728"[0..]; + try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice()); +} + test "toStr: 0" { var dec: RocDec = .{ .num = 0 }; var res_roc_str = dec.toStr(); @@ -953,3 +1049,37 @@ test "div: 10 / 3" { try expectEqual(res, numer.div(denom)); } + +// exports + +pub fn fromF64C(arg: f64) callconv(.C) i128 { + return if (@call(.{ .modifier = always_inline }, RocDec.fromF64, .{arg})) |dec| dec.num else @panic("TODO runtime exception failing convert f64 to RocDec"); +} + +pub fn eqC(arg1: RocDec, arg2: RocDec) callconv(.C) bool { + return @call(.{ .modifier = always_inline }, RocDec.eq, .{ arg1, arg2 }); +} + +pub fn neqC(arg1: RocDec, arg2: RocDec) callconv(.C) bool { + return @call(.{ .modifier = always_inline }, RocDec.neq, .{ arg1, arg2 }); +} + +pub fn negateC(arg: RocDec) callconv(.C) i128 { + return if (@call(.{ .modifier = always_inline }, RocDec.negate, .{arg})) |dec| dec.num else @panic("TODO overflow for negating RocDec"); +} + +pub fn addC(arg1: RocDec, arg2: RocDec) callconv(.C) WithOverflow(RocDec) { + return @call(.{ .modifier = always_inline }, RocDec.addWithOverflow, .{ arg1, arg2 }); +} + +pub fn subC(arg1: RocDec, arg2: RocDec) callconv(.C) WithOverflow(RocDec) { + return @call(.{ .modifier = always_inline }, RocDec.subWithOverflow, .{ arg1, arg2 }); +} + +pub fn mulC(arg1: RocDec, arg2: RocDec) callconv(.C) WithOverflow(RocDec) { + return @call(.{ .modifier = always_inline }, RocDec.mulWithOverflow, .{ arg1, arg2 }); +} + +pub fn divC(arg1: RocDec, arg2: RocDec) callconv(.C) i128 { + return @call(.{ .modifier = always_inline }, RocDec.div, .{ arg1, arg2 }).num; +} diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 602b680d21..22e76054ea 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -3,8 +3,6 @@ const utils = @import("utils.zig"); const RocResult = utils.RocResult; const mem = std.mem; -const TAG_WIDTH = 8; - const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool; const CompareFn = fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) u8; const Opaque = ?[*]u8; @@ -735,6 +733,30 @@ pub fn listAppend(list: RocList, alignment: u32, element: Opaque, element_width: return output; } +pub fn listPrepend(list: RocList, alignment: u32, element: Opaque, element_width: usize) callconv(.C) RocList { + const old_length = list.len(); + var output = list.reallocate(alignment, old_length + 1, element_width); + + // can't use one memcpy here because source and target overlap + if (output.bytes) |target| { + var i: usize = old_length; + + while (i > 0) { + i -= 1; + + // move the ith element to the (i + 1)th position + @memcpy(target + (i + 1) * element_width, target + i * element_width, element_width); + } + + // finally copy in the new first element + if (element) |source| { + @memcpy(target, source, element_width); + } + } + + return output; +} + pub fn listSwap( list: RocList, alignment: u32, diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 71e0ff8df8..c0d050a865 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -5,6 +5,17 @@ const testing = std.testing; // Dec Module const dec = @import("dec.zig"); +comptime { + exportDecFn(dec.fromF64C, "from_f64"); + exportDecFn(dec.eqC, "eq"); + exportDecFn(dec.neqC, "neq"); + exportDecFn(dec.negateC, "negate"); + exportDecFn(dec.addC, "add_with_overflow"); + exportDecFn(dec.subC, "sub_with_overflow"); + exportDecFn(dec.mulC, "mul_with_overflow"); + exportDecFn(dec.divC, "div"); +} + // List Module const list = @import("list.zig"); @@ -22,6 +33,7 @@ comptime { exportListFn(list.listContains, "contains"); exportListFn(list.listRepeat, "repeat"); exportListFn(list.listAppend, "append"); + exportListFn(list.listPrepend, "prepend"); exportListFn(list.listSingle, "single"); exportListFn(list.listJoin, "join"); exportListFn(list.listRange, "range"); @@ -67,6 +79,9 @@ comptime { exportNumFn(num.powInt, "pow_int"); exportNumFn(num.acos, "acos"); exportNumFn(num.asin, "asin"); + exportNumFn(num.bytesToU16C, "bytes_to_u16"); + exportNumFn(num.bytesToU32C, "bytes_to_u32"); + exportNumFn(num.round, "round"); } // Str Module @@ -77,7 +92,7 @@ comptime { exportStrFn(str.countSegments, "count_segments"); exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters"); exportStrFn(str.startsWith, "starts_with"); - exportStrFn(str.startsWithCodePoint, "starts_with_code_point"); + exportStrFn(str.startsWithCodePt, "starts_with_code_point"); exportStrFn(str.endsWith, "ends_with"); exportStrFn(str.strConcatC, "concat"); exportStrFn(str.strJoinWithC, "joinWith"); @@ -85,8 +100,17 @@ comptime { exportStrFn(str.strFromIntC, "from_int"); exportStrFn(str.strFromFloatC, "from_float"); exportStrFn(str.strEqual, "equal"); - exportStrFn(str.strToBytesC, "to_bytes"); + exportStrFn(str.strToUtf8C, "to_utf8"); exportStrFn(str.fromUtf8C, "from_utf8"); + exportStrFn(str.fromUtf8RangeC, "from_utf8_range"); +} + +// Utils +const utils = @import("utils.zig"); +comptime { + exportUtilsFn(utils.test_panic, "test_panic"); + + @export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak }); } // Export helpers - Must be run inside a comptime @@ -102,13 +126,75 @@ fn exportStrFn(comptime func: anytype, comptime func_name: []const u8) void { fn exportDictFn(comptime func: anytype, comptime func_name: []const u8) void { exportBuiltinFn(func, "dict." ++ func_name); } - fn exportListFn(comptime func: anytype, comptime func_name: []const u8) void { exportBuiltinFn(func, "list." ++ func_name); } +fn exportDecFn(comptime func: anytype, comptime func_name: []const u8) void { + exportBuiltinFn(func, "dec." ++ func_name); +} + +fn exportUtilsFn(comptime func: anytype, comptime func_name: []const u8) void { + exportBuiltinFn(func, "utils." ++ func_name); +} + +// Custom panic function, as builtin Zig version errors during LLVM verification +pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn { + std.debug.print("{s}: {?}", .{ message, stacktrace }); + unreachable; +} // Run all tests in imported modules // https://github.com/ziglang/zig/blob/master/lib/std/std.zig#L94 test "" { testing.refAllDecls(@This()); } + +// For unclear reasons, sometimes this function is not linked in on some machines. +// Therefore we provide it as LLVM bitcode and mark it as externally linked during our LLVM codegen +// +// Taken from +// https://github.com/ziglang/zig/blob/85755c51d529e7d9b406c6bdf69ce0a0f33f3353/lib/std/special/compiler_rt/muloti4.zig +// +// Thank you Zig Contributors! +export fn __muloti4(a: i128, b: i128, overflow: *c_int) callconv(.C) i128 { + // @setRuntimeSafety(builtin.is_test); + + const min = @bitCast(i128, @as(u128, 1 << (128 - 1))); + const max = ~min; + overflow.* = 0; + + const r = a *% b; + if (a == min) { + if (b != 0 and b != 1) { + overflow.* = 1; + } + return r; + } + if (b == min) { + if (a != 0 and a != 1) { + overflow.* = 1; + } + return r; + } + + const sa = a >> (128 - 1); + const abs_a = (a ^ sa) -% sa; + const sb = b >> (128 - 1); + const abs_b = (b ^ sb) -% sb; + + if (abs_a < 2 or abs_b < 2) { + return r; + } + + if (sa == sb) { + if (abs_a > @divTrunc(max, abs_b)) { + overflow.* = 1; + } + } else { + if (abs_a > @divTrunc(min, -abs_b)) { + overflow.* = 1; + } + } + + return r; +} diff --git a/compiler/builtins/bitcode/src/num.zig b/compiler/builtins/bitcode/src/num.zig index b42aee20ca..94873f2fe6 100644 --- a/compiler/builtins/bitcode/src/num.zig +++ b/compiler/builtins/bitcode/src/num.zig @@ -1,6 +1,7 @@ const std = @import("std"); const always_inline = std.builtin.CallOptions.Modifier.always_inline; const math = std.math; +const RocList = @import("list.zig").RocList; pub fn atan(num: f64) callconv(.C) f64 { return @call(.{ .modifier = always_inline }, math.atan, .{num}); @@ -21,3 +22,25 @@ pub fn acos(num: f64) callconv(.C) f64 { pub fn asin(num: f64) callconv(.C) f64 { return @call(.{ .modifier = always_inline }, math.asin, .{num}); } + +pub fn bytesToU16C(arg: RocList, position: usize) callconv(.C) u16 { + return @call(.{ .modifier = always_inline }, bytesToU16, .{ arg, position }); +} + +fn bytesToU16(arg: RocList, position: usize) u16 { + const bytes = @ptrCast([*]const u8, arg.bytes); + return @bitCast(u16, [_]u8{ bytes[position], bytes[position + 1] }); +} + +pub fn bytesToU32C(arg: RocList, position: usize) callconv(.C) u32 { + return @call(.{ .modifier = always_inline }, bytesToU32, .{ arg, position }); +} + +fn bytesToU32(arg: RocList, position: usize) u32 { + const bytes = @ptrCast([*]const u8, arg.bytes); + return @bitCast(u32, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3] }); +} + +pub fn round(num: f64) callconv(.C) i64 { + return @floatToInt(i32, (@round(num))); +} diff --git a/compiler/builtins/bitcode/src/str.zig b/compiler/builtins/bitcode/src/str.zig index a52f4dd33b..5f21c91da5 100644 --- a/compiler/builtins/bitcode/src/str.zig +++ b/compiler/builtins/bitcode/src/str.zig @@ -865,8 +865,8 @@ pub fn startsWith(string: RocStr, prefix: RocStr) callconv(.C) bool { return true; } -// Str.startsWithCodePoint -pub fn startsWithCodePoint(string: RocStr, prefix: u32) callconv(.C) bool { +// Str.startsWithCodePt +pub fn startsWithCodePt(string: RocStr, prefix: u32) callconv(.C) bool { const bytes_len = string.len(); const bytes_ptr = string.asU8ptr(); @@ -886,18 +886,18 @@ pub fn startsWithCodePoint(string: RocStr, prefix: u32) callconv(.C) bool { return true; } -test "startsWithCodePoint: ascii char" { +test "startsWithCodePt: ascii char" { const whole = RocStr.init("foobar", 6); const prefix = 'f'; - try expect(startsWithCodePoint(whole, prefix)); + try expect(startsWithCodePt(whole, prefix)); } -test "startsWithCodePoint: emoji" { +test "startsWithCodePt: emoji" { const yes = RocStr.init("💖foobar", 10); const no = RocStr.init("foobar", 6); const prefix = '💖'; - try expect(startsWithCodePoint(yes, prefix)); - try expect(!startsWithCodePoint(no, prefix)); + try expect(startsWithCodePt(yes, prefix)); + try expect(!startsWithCodePt(no, prefix)); } test "startsWith: foo starts with fo" { @@ -1129,8 +1129,8 @@ test "RocStr.joinWith: result is big" { try expect(roc_result.eq(result)); } -// Str.toBytes -pub fn strToBytesC(arg: RocStr) callconv(.C) RocList { +// Str.toUtf8 +pub fn strToUtf8C(arg: RocStr) callconv(.C) RocList { return @call(.{ .modifier = always_inline }, strToBytes, .{arg}); } @@ -1156,6 +1156,11 @@ const FromUtf8Result = extern struct { problem_code: Utf8ByteProblem, }; +const CountAndStart = extern struct { + count: usize, + start: usize, +}; + pub fn fromUtf8C(arg: RocList, output: *FromUtf8Result) callconv(.C) void { output.* = @call(.{ .modifier = always_inline }, fromUtf8, .{arg}); } @@ -1192,6 +1197,24 @@ fn fromUtf8(arg: RocList) FromUtf8Result { } } +pub fn fromUtf8RangeC(arg: RocList, countAndStart: CountAndStart, output: *FromUtf8Result) callconv(.C) void { + output.* = @call(.{ .modifier = always_inline }, fromUtf8Range, .{ arg, countAndStart }); +} + +fn fromUtf8Range(arg: RocList, countAndStart: CountAndStart) FromUtf8Result { + const bytes = @ptrCast([*]const u8, arg.bytes)[countAndStart.start..countAndStart.count]; + + if (unicode.utf8ValidateSlice(bytes)) { + // the output will be correct. Now we need to clone the input + const string = RocStr.init(@ptrCast([*]const u8, bytes), countAndStart.count); + + return FromUtf8Result{ .is_ok = true, .string = string, .byte_index = 0, .problem_code = Utf8ByteProblem.InvalidStartByte }; + } else { + const temp = errorToProblem(@ptrCast([*]u8, arg.bytes), arg.length); + return FromUtf8Result{ .is_ok = false, .string = RocStr.empty(), .byte_index = temp.index, .problem_code = temp.problem }; + } +} + fn errorToProblem(bytes: [*]u8, length: usize) struct { index: usize, problem: Utf8ByteProblem } { var index: usize = 0; diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index b679c20153..f83fbfd227 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -1,6 +1,10 @@ const std = @import("std"); const always_inline = std.builtin.CallOptions.Modifier.always_inline; +pub fn WithOverflow(comptime T: type) type { + return extern struct { value: T, has_overflowed: bool }; +} + // If allocation fails, this must cxa_throw - it must not return a null pointer! extern fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void; @@ -11,12 +15,16 @@ extern fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignmen // This should never be passed a null pointer. extern fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void; +// Signals to the host that the program has paniced +extern fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void; + comptime { // During tetsts, use the testing allocators to satisfy these functions. if (std.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 }); + @export(testing_roc_panic, .{ .name = "roc_panic", .linkage = .Strong }); } } @@ -37,6 +45,10 @@ fn testing_roc_dealloc(c_ptr: *c_void, _: u32) callconv(.C) void { std.testing.allocator.destroy(ptr); } +fn testing_roc_panic(c_ptr: *c_void, _: u32) callconv(.C) void { + @panic("Roc paniced"); +} + pub fn alloc(size: usize, alignment: u32) [*]u8 { return @ptrCast([*]u8, @call(.{ .modifier = always_inline }, roc_alloc, .{ size, alignment })); } @@ -49,6 +61,22 @@ pub fn dealloc(c_ptr: [*]u8, alignment: u32) void { return @call(.{ .modifier = always_inline }, roc_dealloc, .{ c_ptr, alignment }); } +// must export this explicitly because right now it is not used from zig code +pub fn panic(c_ptr: *c_void, alignment: u32) callconv(.C) void { + return @call(.{ .modifier = always_inline }, roc_panic, .{ c_ptr, alignment }); +} + +// indirection because otherwise zig creats an alias to the panic function which our LLVM code +// does not know how to deal with +pub fn test_panic(c_ptr: *c_void, alignment: u32) callconv(.C) void { + const cstr = @ptrCast([*:0]u8, c_ptr); + + // const stderr = std.io.getStdErr().writer(); + // stderr.print("Roc panicked: {s}!\n", .{cstr}) catch unreachable; + + std.c.exit(1); +} + pub const Inc = fn (?[*]u8) callconv(.C) void; pub const IncN = fn (?[*]u8, u64) callconv(.C) void; pub const Dec = fn (?[*]u8) callconv(.C) void; diff --git a/compiler/builtins/docs/Bool.roc b/compiler/builtins/docs/Bool.roc index 077909e84d..642733ec3d 100644 --- a/compiler/builtins/docs/Bool.roc +++ b/compiler/builtins/docs/Bool.roc @@ -55,7 +55,7 @@ and : Bool, Bool -> Bool ## ## In some languages, `&&` and `||` are special-cased in the compiler to skip ## evaluating the expression after the operator under certain circumstances. -## # In Roc, this is not the case. See the performance notes for [Bool.and] for details. +## In Roc, this is not the case. See the performance notes for [Bool.and] for details. or : Bool, Bool -> Bool ## Exclusive or @@ -73,7 +73,7 @@ xor : Bool, Bool -> Bool ## 2. Private tags are equal if they are the same tag, in the same module, and also their contents (if any) are equal. ## 3. Records are equal if all their fields are equal. ## 4. Collections ([Str], [List], [Dict], and [Set]) are equal if they are the same length, and also all their corresponding elements are equal. -## 5. [Num] values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `False`. See [Num.isNaN] for more about *NaN*. +## 5. [Num] values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `False`. See `Num.isNaN` for more about *NaN*. ## ## Note that `isEq` takes `'val` instead of `val`, which means `isEq` does not ## accept arguments whose types contain functions. diff --git a/compiler/builtins/docs/Dict.roc b/compiler/builtins/docs/Dict.roc index 3aad9e4eee..05b1f82815 100644 --- a/compiler/builtins/docs/Dict.roc +++ b/compiler/builtins/docs/Dict.roc @@ -1,5 +1,21 @@ interface Dict - exposes [ isEmpty, map ] + exposes + [ + Dict, + empty, + single, + get, + walk, + insert, + len, + remove, + contains, + keys, + values, + union, + intersection, + difference + ] imports [] size : Dict * * -> Nat @@ -14,7 +30,7 @@ isEmpty : Dict * * -> Bool ## >>> Dict.map {[ "", "a", "bc" ]} Str.isEmpty ## ## `map` functions like this are common in Roc, and they all work similarly. -## See for example #Result.map, #List.map, and #Set.map. +## See for example [List.map], [Result.map], and `Set.map`. map : Dict beforeKey beforeValue, ({ key: beforeKey, value: beforeValue } -> { key: afterKey, value: afterValue }) diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index 2f3248da4b..f8f9a2df43 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -1,79 +1,64 @@ -interface List2 +interface List exposes - [ List - , single - , empty - , repeat - , range - , reverse - , sort - , map - , mapWithIndex - , mapOrCancel - , mapOks - , update - , updater - , allOks - , append - , prepend - , concat - , join - , joinMap - , oks - , zip - , zipMap - , keepIf - , dropIf - , first - , last - , get - , max - , min - , put - , drop - , append - , prepend - , dropLast - , dropFirst - , takeFirst - , takeLast - , split - , sublist - , walk - , walkBackwards - , walkUntil - , walkBackwardsUntil - , len - , isEmpty - , contains - , all - , any + [ + List, + isEmpty, + get, + set, + append, + map, + len, + walkBackwards, + concat, + first, + single, + repeat, + reverse, + prepend, + join, + keepIf, + contains, + sum, + walk, + last, + keepOks, + keepErrs, + mapWithIndex, + map2, + map3, + product, + walkUntil, + range, + sortWith, + drop, + swap ] imports [] ## Types ## A sequential list of values. -## # >>> [ 1, 2, 3 ] # a list of numbers # # >>> [ "a", "b", "c" ] # a list of strings ## -## >>> [ [ 1.1 ], [], [ 2.2, 3.3 ] ] # a list of lists of floats +## >>> [ 1, 2, 3 ] # a list of numbers +## >>> [ "a", "b", "c" ] # a list of strings +## >>> [ [ 1.1 ], [], [ 2.2, 3.3 ] ] # a list of lists of numbers ## -## The list [ 1, "a" ] gives an error, because each element in a list must have -## the same type. If you want to put a mix of #Int and #Str values into a list, try this: +## The list `[ 1, "a" ]` gives an error, because each element in a list must have +## the same type. If you want to put a mix of [I64] and [Str] values into a list, try this: ## ## ``` ## mixedList : List [ IntElem I64, StrElem Str ]* ## mixedList = [ IntElem 1, IntElem 2, StrElem "a", StrElem "b" ] ## ``` ## -## The maximum size of a #List is limited by the amount of heap memory available +## The maximum size of a [List] is limited by the amount of heap memory available ## to the current process. If there is not enough memory available, attempting to ## create the list could crash. (On Linux, where [overcommit](https://www.etalabs.net/overcommit.html) ## is normally enabled, not having enough memory could result in the list appearing ## to be created just fine, but then crashing later.) ## -## > The theoretical maximum length for a list created in Roc is -## > #Int.maxNat divided by 2. Attempting to create a list bigger than that +## > The theoretical maximum length for a list created in Roc is half of +## > `Num.maxNat`. Attempting to create a list bigger than that ## > in Roc code will always fail, although in practice it is likely to fail ## > at much smaller lengths due to insufficient memory being available. ## @@ -147,13 +132,13 @@ interface List2 ## ## List.first (getRatings 5).bar ## -## This call to #List.first means that even the list in the `bar` field has become +## This call to [List.first] means that even the list in the `bar` field has become ## inaccessible. As such, this line will cause the list's refcount to get ## decremented all the way to 0. At that point, nothing is referencing the list ## anymore, and its memory will get freed. ## ## Things are different if this is a list of lists instead of a list of numbers. -## Let's look at a simpler example using #List.first - first with a list of numbers, +## Let's look at a simpler example using [List.first] - first with a list of numbers, ## and then with a list of lists, to see how they differ. ## ## Here's the example using a list of numbers. @@ -165,7 +150,7 @@ interface List2 ## ## first ## -## It makes a list, calls #List.first and #List.last on it, and then returns `first`. +## It makes a list, calls [List.first] and [List.last] on it, and then returns `first`. ## ## Here's the equivalent code with a list of lists: ## @@ -189,12 +174,12 @@ interface List2 ## their own refcounts - to go inside that list. (The empty list at the end ## does not use heap memory, and thus has no refcount.) ## -## At the end, we once again call #List.first on the list, but this time +## At the end, we once again call [List.first] on the list, but this time ## ## * Copying small lists (64 elements or fewer) is typically slightly faster than copying small persistent data structures. This is because, at small sizes, persistent data structures tend to be thin wrappers around flat arrays anyway. They don't have any copying advantage until crossing a certain minimum size threshold. -## * Even when copying is faster, other list operations may still be slightly slower with persistent data structures. For example, even if it were a persistent data structure, #List.map, #List.fold, and #List.keepIf would all need to traverse every element in the list and build up the result from scratch. These operations are all -## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, #List.map, #List.keepIf, and #List.set can all be optimized to perform in-place mutations. -## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way #List worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using #List under the hood! +## * Even when copying is faster, other list operations may still be slightly slower with persistent data structures. For example, even if it were a persistent data structure, [List.map], [List.walk], and [List.keepIf] would all need to traverse every element in the list and build up the result from scratch. These operations are all +## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, [List.map], [List.keepIf], and [List.set] can all be optimized to perform in-place mutations. +## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way [List] worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using [List] under the hood! List elem : [ @List elem ] ## Initialize @@ -262,18 +247,18 @@ sortDesc : List elem, (elem -> Num *) -> List elem ## > List.map [ "", "a", "bc" ] Str.isEmpty ## ## `map` functions like this are common in Roc, and they all work similarly. -## See for example #Result.map, #Set.map, and #Map.map. +## See for example `Set.map`, `Dict.map`, and [Result.map]. map : List before, (before -> after) -> List after -## This works like #List.map, except it also passes the index +## 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 -## This works like #List.map, except at any time you can return `Err` to +## This works like [List.map], except at any time you can return `Err` to ## cancel the entire operation immediately, and return that #Err. mapOrCancel : List before, (before -> Result after err) -> Result (List after) err -## This works like #List.map, except only the transformed values that are +## This works like [List.map], except only the transformed values that are ## wrapped in `Ok` are kept. Any that are wrapped in `Err` are dropped. ## ## >>> List.mapOks [ [ "a", "b" ], [], [], [ "c", "d", "e" ] ] List.last @@ -287,16 +272,16 @@ mapOks : List before, (before -> Result after *) -> List after ## the given function. ## ## For a version of this which gives you more control over when to perform -## the transformation, see #List.updater +## the transformation, see `List.updater` ## ## ## Performance notes ## ## In particular when updating nested collections, this is potentially much more -## efficient than using #List.get to obtain the element, transforming it, +## efficient than using [List.get] to obtain the element, transforming it, ## and then putting it back in the same place. update : List elem, Nat, (elem -> elem) -> List elem -## A more flexible version of #List.update, which returns an "updater" function +## A more flexible version of `List.update`, which returns an "updater" function ## that lets you delay performing the update until later. updater : List elem, Nat -> { elem, new : (elem -> List elem) } @@ -337,15 +322,15 @@ concat : List elem, List elem -> List elem ## >>> List.join [] join : List (List elem) -> List elem -## Like #List.map, except the transformation function wraps the return value +## Like [List.map], except the transformation function wraps the return value ## in a list. At the end, all the lists get joined together into one list. joinMap : List before, (before -> List after) -> List after -## Like #List.join, but only keeps elements tagged with `Ok`. Elements +## Like [List.join], but only keeps elements tagged with `Ok`. Elements ## tagged with `Err` are dropped. ## ## This can be useful after using an operation that returns a #Result -## on each element of a list, for example #List.first: +## on each element of a list, for example [List.first]: ## ## >>> [ [ 1, 2, 3 ], [], [], [ 4, 5 ] ] ## >>> |> List.map List.first @@ -387,16 +372,16 @@ zipMap : List a, List b, (a, b -> c) -> List c ## ## ## Performance Details ## -## #List.keepIf always returns a list that takes up exactly the same amount +## [List.keepIf] always returns a list that takes up exactly the same amount ## of memory as the original, even if its length decreases. This is becase it ## can't know in advance exactly how much space it will need, and if it guesses a ## length that's too low, it would have to re-allocate. ## ## (If you want to do an operation like this which reduces the memory footprint -## of the resulting list, you can do two passes over the lis with #List.fold - one +## of the resulting list, you can do two passes over the lis with [List.walk] - one ## to calculate the precise new size, and another to populate the new list.) ## -## If given a unique list, #List.keepIf will mutate it in place to assemble the appropriate list. +## If given a unique list, [List.keepIf] will mutate it in place to assemble the appropriate list. ## If that happens, this function will not allocate any new memory on the heap. ## If all elements in the list end up being kept, Roc will return the original ## list unaltered. @@ -410,7 +395,7 @@ keepIf : List elem, (elem -> Bool) -> List elem ## ## ## Performance Details ## -## #List.dropIf has the same performance characteristics as #List.keepIf. +## `List.dropIf` has the same performance characteristics as [List.keepIf]. ## See its documentation for details on those characteristics! dropIf : List elem, (elem -> Bool) -> List elem @@ -437,14 +422,14 @@ min : List (Num a) -> Result (Num a) [ ListWasEmpty ]* ## If the given index is outside the bounds of the list, returns the original ## list unmodified. ## -## To drop the element at a given index, instead of replacing it, see #List.drop. +## To drop the element at a given index, instead of replacing it, see [List.drop]. set : List elem, Nat, elem -> List elem ## Drops the element at the given index from the list. ## ## This has no effect if the given index is outside the bounds of the list. ## -## To replace the element at a given index, instead of dropping it, see #List.set. +## To replace the element at a given index, instead of dropping it, see [List.set]. drop : List elem, Nat -> List elem ## Adds a new element to the end of the list. @@ -466,12 +451,12 @@ append : List elem, elem -> List elem ## ## Performance Details ## ## This always clones the entire list, even when given a Unique list. That means -## it runs about as fast as #List.addLast when both are given a Shared list. +## it runs about as fast as `List.addLast` when both are given a Shared list. ## -## If you have a Unique list instead, #List.append will run much faster than -## #List.prepend except in the specific case where the list has no excess capacity, -## and needs to *clone and grow*. In that uncommon case, both #List.append and -## #List.prepend will run at about the same speed—since #List.prepend always +## If you have a Unique list instead, [List.append] will run much faster than +## [List.append] except in the specific case where the list has no excess capacity, +## and needs to *clone and grow*. In that uncommon case, both [List.append] and +## [List.append] will run at about the same speed—since [List.append] always ## has to clone and grow. ## ## | Unique list | Shared list | @@ -493,11 +478,11 @@ prepend : List elem, elem -> List elem ## ## ## Performance Details ## -## Calling #List.pop on a Unique list runs extremely fast. It's essentially -## the same as a #List.last except it also returns the #List it was given, +## Calling `List.pop` on a Unique list runs extremely fast. It's essentially +## the same as a [List.last] except it also returns the [List] it was given, ## with its length decreased by 1. ## -## In contrast, calling #List.pop on a Shared list creates a new list, then +## In contrast, calling `List.pop` on a Shared list creates a new list, then ## copies over every element in the original list except the last one. This ## takes much longer. dropLast : List elem -> Result { others : List elem, last : elem } [ ListWasEmpty ]* @@ -511,8 +496,8 @@ dropLast : List elem -> Result { others : List elem, last : elem } [ ListWasEmpt ## ## ## Performance Details ## -## When calling either #List.dropFirst or #List.dropLast on a Unique list, #List.dropLast -## runs *much* faster. This is because for #List.dropLast, removing the last element +## When calling either `List.dropFirst` or `List.dropLast` on a Unique list, `List.dropLast` +## runs *much* faster. This is because for `List.dropLast`, removing the last element ## in-place is as easy as reducing the length of the list by 1. In contrast, ## removing the first element from the list involves copying every other element ## in the list into the index before it - which is massively more costly. @@ -521,8 +506,8 @@ dropLast : List elem -> Result { others : List elem, last : elem } [ ListWasEmpt ## ## | Unique list | Shared list | ##-----------+----------------------------------+---------------------------------+ -## dropFirst | #List.last + length change | #List.last + clone rest of list | -## dropLast | #List.last + clone rest of list | #List.last + clone rest of list | +## dropFirst | [List.last] + length change | [List.last] + clone rest of list | +## dropLast | [List.last] + clone rest of list | [List.last] + clone rest of list | dropFirst : List elem -> Result { first: elem, others : List elem } [ ListWasEmpty ]* ## Returns the given number of elements from the beginning of the list. @@ -534,21 +519,21 @@ dropFirst : List elem -> Result { first: elem, others : List elem } [ ListWasEmp ## ## >>> List.takeFirst 5 [ 1, 2 ] ## -## To *remove* elements from the beginning of the list, use #List.takeLast. +## To *remove* elements from the beginning of the list, use `List.takeLast`. ## ## To remove elements from both the beginning and end of the list, -## use #List.sublist. +## use `List.sublist`. ## -## To split the list into two lists, use #List.split. +## To split the list into two lists, use `List.split`. ## ## ## Performance Details ## ## When given a Unique list, this runs extremely fast. It sets the list's length ## to the given length value, and frees the leftover elements. This runs very -## slightly faster than #List.takeLast. +## slightly faster than `List.takeLast`. ## ## In fact, `List.takeFirst 1 list` runs faster than `List.first list` when given -## a Unique list, because #List.first returns the first element as well - +## a Unique list, because [List.first] returns the first element as well - ## which introduces a conditional bounds check as well as a memory load. takeFirst : List elem, Nat -> List elem @@ -561,22 +546,22 @@ takeFirst : List elem, Nat -> List elem ## ## >>> List.takeLast 5 [ 1, 2 ] ## -## To *remove* elements from the end of the list, use #List.takeFirst. +## To *remove* elements from the end of the list, use `List.takeFirst`. ## ## To remove elements from both the beginning and end of the list, -## use #List.sublist. +## use `List.sublist`. ## -## To split the list into two lists, use #List.split. +## To split the list into two lists, use `List.split`. ## ## ## Performance Details ## ## When given a Unique list, this runs extremely fast. It moves the list's ## pointer to the index at the given length value, updates its length, ## and frees the leftover elements. This runs very nearly as fast as -## #List.takeFirst on a Unique list. +## `List.takeFirst` on a Unique list. ## ## In fact, `List.takeLast 1 list` runs faster than `List.first list` when given -## a Unique list, because #List.first returns the first element as well - +## a Unique list, because [List.first] returns the first element as well - ## which introduces a conditional bounds check as well as a memory load. takeLast : List elem, Nat -> List elem @@ -603,7 +588,7 @@ split : List elem, Nat -> { before: List elem, others: List elem } ## >>> List.sublist { start: 2, len: 10 } [ 1, 2, 3, 4, 5 ] ## ## > If you want a sublist which goes all the way to the end of the list, no -## > matter how long the list is, #List.takeLast can do that more efficiently. +## > matter how long the list is, `List.takeLast` can do that more efficiently. ## ## Some languages have a function called **`slice`** which works similarly to this. sublist : List elem, { start : Nat, len : Nat } -> List elem @@ -623,7 +608,7 @@ sublist : List elem, { start : Nat, len : Nat } -> List elem ## * `state` starts at 0 (because of `start: 0`) ## * Each `step` runs `Num.add state elem`, and the return value becomes the new `state`. ## -## Here is a table of how `state` changes as #List.walk walks over the elements +## Here is a table of how `state` changes as [List.walk] walks over the elements ## `[ 2, 4, 8 ]` using #Num.add as its `step` function to determine the next `state`. ## ## `state` | `elem` | `step state elem` (`Num.add state elem`) @@ -650,27 +635,27 @@ walk : List elem, { start : state, step : (state, elem -> state) } -> state ## `fold`, `foldRight`, or `foldr`. walkBackwards : List elem, { start : state, step : (state, elem -> state) } -> state -## Same as #List.walk, except you can stop walking early. +## Same as [List.walk], except you can stop walking early. ## ## ## Performance Details ## -## Compared to #List.walk, this can potentially visit fewer elements (which can +## Compared to [List.walk], this can potentially visit fewer elements (which can ## improve performance) at the cost of making each step take longer. ## However, the added cost to each step is extremely small, and can easily ## be outweighed if it results in skipping even a small number of elements. ## -## As such, it is typically better for performance to use this over #List.walk +## As such, it is typically better for performance to use this over [List.walk] ## if returning `Done` earlier than the last element is expected to be common. walkUntil : List elem, { start : state, step : (state, elem -> [ Continue state, Done state ]) } -> state -# Same as #List.walkBackwards, except you can stop walking early. +# Same as [List.walk]Backwards, except you can stop walking early. walkBackwardsUntil : List elem, { start : state, step : (state, elem -> [ Continue state, Done state ]) } -> state ## Check ## Returns the length of the list - the number of elements it contains. ## -## One #List can store up to 2,147,483,648 elements (just over 2 billion), which +## One [List] can store up to 2,147,483,648 elements (just over 2 billion), which ## is exactly equal to the highest valid #I32 value. This means the #U32 this function ## returns can always be safely converted to an #I32 without losing any data. len : List * -> Nat diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index db8d41bd7f..530b91c62f 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -1,10 +1,97 @@ -interface Num2 - exposes [ Num, neg, abs, add, sub, mul, isOdd, isEven, isPositive, isNegative, isZero ] +interface Num + exposes + [ + Num, + Int, + Float, + Natural, + Nat, + Decimal, + Dec, + Integer, + FloatingPoint, + I128, + U128, + I64, + U64, + I32, + U32, + I16, + U16, + I8, + U8, + F64, + F32, + maxInt, + minInt, + maxFloat, + minFloat, + abs, + neg, + add, + sub, + mul, + isLt, + isLte, + isGt, + isGte, + toFloat, + sin, + cos, + tan, + isZero, + isEven, + isOdd, + isPositive, + isNegative, + rem, + div, + divFloor, + modInt, + modFloat, + sqrt, + log, + round, + compare, + pow, + ceiling, + powInt, + floor, + addWrap, + addChecked, + atan, + acos, + Signed128, + Signed64, + Signed32, + Signed16, + Signed8, + Unsigned128, + Unsigned64, + Unsigned32, + Unsigned16, + Unsigned8, + Binary64, + Binary32, + bitwiseAnd, + bitwiseXor, + bitwiseOr, + shiftLeftBy, + shiftRightBy, + shiftRightZfBy, + subWrap, + subChecked, + mulWrap, + mulChecked, + intCast, + maxI128, + isMultipleOf + ] imports [] -## Types +## ## Types -## Represents a number that could be either an [Int] or a [Frac]. +## Represents a number that could be either an [Int] or a [Float]. ## ## This is useful for functions that can work on either, for example #Num.add, whose type is: ## @@ -19,8 +106,8 @@ interface Num2 ## technically has the type `Num (Integer *)`, so when you pass two of them to ## [Num.add], the answer you get is `2 : Num (Integer *)`. ## -## The type [`Frac a`](#Frac) is defined to be an alias for `Num (Fraction a)`, -## so `3.0 : Num (Fraction *)` is the same value as `3.0 : Frac *`. +## The type [`Float a`]([Float]) is defined to be an alias for `Num (Fraction a)`, +## so `3.0 : Num (Fraction *)` is the same value as `3.0 : Float *`. ## Similarly, the type [`Int a`](#Int) is defined to be an alias for ## `Num (Integer a)`, so `2 : Num (Integer *)` is the same value as ## `2 : Int *`. @@ -40,7 +127,7 @@ interface Num2 ## ends up having the type `Nat`. ## ## Sometimes number literals don't become more specific. For example, -## the [Num.toStr] function has the type `Num * -> Str`. This means that +## the `Num.toStr` function has the type `Num * -> Str`. This means that ## when calling `Num.toStr (5 + 6)`, the expression `(5 + 6)` ## still has the type `Num *`. When this happens, `Num *` defaults to ## being an [I64] - so this addition expression would overflow @@ -97,7 +184,7 @@ Num a : [ @Num a ] ## [Dec] typically takes slightly less time than [F64] to perform addition and ## subtraction, but 10-20 times longer to perform multiplication and division. ## [sqrt] and trigonometry are massively slower with [Dec] than with [F64]. -Dec : Frac [ @Decimal128 ] +Dec : Float [ @Decimal128 ] ## A fixed-size number with a fractional component. ## @@ -166,7 +253,7 @@ Dec : Frac [ @Decimal128 ] ## loops and conditionals. If you need to do performance-critical trigonometry ## or square roots, either [F64] or [F32] is probably a better choice than the ## usual default choice of [Dec], despite the precision problems they bring. -Frac a : Num [ @Fraction a ] +Float a : Num [ @Fraction a ] ## A fixed-size integer - that is, a number with no fractional component. ## @@ -331,7 +418,7 @@ Int size : Num [ @Int size ] ## ## >>> Num.neg 0.0 ## -## This is safe to use with any #Frac, but it can cause overflow when used with certain #Int values. +## This is safe to use with any [Float], but it can cause overflow when used with certain #Int values. ## ## For example, calling #Num.neg on the lowest value of a signed integer (such as #Int.lowestI64 or #Int.lowestI32) will cause overflow. ## This is because, for any given size of signed integer (32-bit, 64-bit, etc.) its negated lowest value turns out to be 1 higher than @@ -339,7 +426,7 @@ Int size : Num [ @Int size ] ## ## Additionally, calling #Num.neg on any unsigned integer (such as any #U64 or #U32 value) other than zero will cause overflow. ## -## (It will never crash when given a #Frac, however, because of how floating point numbers represent positive and negative numbers.) +## (It will never crash when given a [Float], however, because of how floating point numbers represent positive and negative numbers.) neg : Num a -> Num a ## Return the absolute value of the number. @@ -356,7 +443,7 @@ neg : Num a -> Num a ## ## >>> Num.abs 0.0 ## -## This is safe to use with any #Frac, but it can cause overflow when used with certain #Int values. +## This is safe to use with any [Float], but it can cause overflow when used with certain #Int values. ## ## For example, calling #Num.abs on the lowest value of a signed integer (such as #Int.lowestI64 or #Int.lowestI32) will cause overflow. ## This is because, for any given size of signed integer (32-bit, 64-bit, etc.) its negated lowest value turns out to be 1 higher than @@ -390,7 +477,7 @@ isOdd : Num * -> Bool ## Add two numbers of the same type. ## -## (To add an #Int and a #Frac, first convert one so that they both have the same type. There are functions in the [`Frac`](/Frac) module that can convert both #Int to #Frac and the other way around.) +## (To add an #Int and a [Float], first convert one so that they both have the same type. There are functions in the [`Frac`](/Frac) module that can convert both #Int to [Float] and the other way around.) ## ## `a + b` is shorthand for `Num.add a b`. ## @@ -417,7 +504,7 @@ addCheckOverflow : Num a, Num a -> Result (Num a) [ Overflow ]* ## Subtract two numbers of the same type. ## -## (To subtract an #Int and a #Frac, first convert one so that they both have the same type. There are functions in the [`Frac`](/Frac) module that can convert both #Int to #Frac and the other way around.) +## (To subtract an #Int and a [Float], first convert one so that they both have the same type. There are functions in the [`Frac`](/Frac) module that can convert both #Int to [Float] and the other way around.) ## ## `a - b` is shorthand for `Num.sub a b`. ## @@ -444,7 +531,7 @@ subCheckOverflow : Num a, Num a -> Result (Num a) [ Overflow ]* ## Multiply two numbers of the same type. ## -## (To multiply an #Int and a #Frac, first convert one so that they both have the same type. There are functions in the [`Frac`](/Frac) module that can convert both #Int to #Frac and the other way around.) +## (To multiply an #Int and a [Float], first convert one so that they both have the same type. There are functions in the [`Frac`](/Frac) module that can convert both #Int to [Float] and the other way around.) ## ## `a * b` is shorthand for `Num.mul a b`. ## @@ -478,7 +565,7 @@ mulCheckOverflow : Num a, Num a -> Result (Num a) [ Overflow ]* ## ## >>> Num.toStr 42 ## -## Only #Frac values will include a decimal point, and they will always include one. +## Only [Float] values will include a decimal point, and they will always include one. ## ## >>> Num.toStr 4.2 ## @@ -553,16 +640,16 @@ format : -> Str ## Round off the given float to the nearest integer. -round : Frac * -> Int * -ceil : Frac * -> Int * -floor : Frac * -> Int * -trunc : Frac * -> Int * +round : Float * -> Int * +ceil : Float * -> Int * +floor : Float * -> Int * +trunc : Float * -> Int * ## Convert an #Int to a #Nat. If the given number doesn't fit in #Nat, it will be truncated. ## Since #Nat has a different maximum number depending on the system you're building ## for, this may give a different answer on different systems. ## -## For example, on a 32-bit system, #Num.maxNat will return the same answer as +## For example, on a 32-bit system, [Num.maxNat] will return the same answer as ## #Num.maxU32. This means that calling `Num.toNat 9_000_000_000` on a 32-bit ## system will return #Num.maxU32 instead of 9 billion, because 9 billion is ## higher than #Num.maxU32 and will not fit in a #Nat on a 32-bit system. @@ -571,13 +658,13 @@ trunc : Frac * -> Int * ## the #Nat value of 9_000_000_000. This is because on a 64-bit system, #Nat can ## hold up to #Num.maxU64, and 9_000_000_000 is lower than #Num.maxU64. ## -## To convert a #Frac to a #Nat, first call either #Num.round, #Num.ceil, or #Num.floor +## To convert a [Float] to a #Nat, first call either #Num.round, #Num.ceil, or #Num.floor ## on it, then call this on the resulting #Int. toNat : Int * -> Nat ## Convert an #Int to an #I8. If the given number doesn't fit in #I8, it will be truncated. ## -## To convert a #Frac to an #I8, first call either #Num.round, #Num.ceil, or #Num.floor +## To convert a [Float] to an #I8, first call either #Num.round, #Num.ceil, or #Num.floor ## on it, then call this on the resulting #Int. toI8 : Int * -> I8 toI16 : Int * -> I16 @@ -633,9 +720,9 @@ divRound : Int a, Int a -> Int a ## Modulo is the same as remainder when working with positive numbers, ## but if either number is negative, then modulo works differently. ## -## Additionally, flooring modulo uses #Frac.floor on the result. +## Additionally, flooring modulo uses [Float].floor on the result. ## -## (Use #Frac.mod for non-flooring modulo.) +## (Use [Float].mod for non-flooring modulo.) ## ## Return `Err DivByZero` if the second integer is zero, because division by zero is undefined in mathematics. ## @@ -749,28 +836,28 @@ maxDec : Dec ## Constants ## An approximation of e, specifically 2.718281828459045. -e : Frac * +e : Float * ## An approximation of pi, specifically 3.141592653589793. -pi : Frac * +pi : Float * ## Trigonometry -cos : Frac a -> Frac a +cos : Float a -> Float a -acos : Frac a -> Frac a +acos : Float a -> Float a -sin : Frac a -> Frac a +sin : Float a -> Float a -asin : Frac a -> Frac a +asin : Float a -> Float a -tan : Frac a -> Frac a +tan : Float a -> Float a -atan : Frac a -> Frac a +atan : Float a -> Float a ## Other Calculations (arithmetic?) -## Divide one [Frac] by another. +## Divide one [Float] by another. ## ## `a / b` is shorthand for `Num.div a b`. ## @@ -789,7 +876,7 @@ atan : Frac a -> Frac a ## > cost! Since the most common reason to choose [F64] or [F32] over [Dec] is ## > access to hardware-accelerated performance, Roc follows these rules exactly. ## -## To divide an [Int] and a [Frac], first convert the [Int] to a [Frac] using +## To divide an [Int] and a [Float], first convert the [Int] to a [Float] using ## one of the functions in this module like [toDec]. ## ## >>> 5.0 / 7.0 @@ -800,9 +887,9 @@ atan : Frac a -> Frac a ## ## >>> Num.pi ## >>> |> Num.div 2.0 -div : Frac a, Frac a -> Frac a +div : Float a, Float a -> Float a -## Perform modulo on two [Frac]s. +## Perform modulo on two [Float]s. ## ## Modulo is the same as remainder when working with positive numbers, ## but if either number is negative, then modulo works differently. @@ -825,20 +912,20 @@ div : Frac a, Frac a -> Frac a ## ## >>> Num.pi ## >>> |> Num.mod 2.0 -mod : Frac a, Frac a -> Frac a +mod : Float a, Float a -> Float a -## Raises a #Frac to the power of another #Frac. +## Raises a [Float] to the power of another [Float]. ## ## ` ## For an #Int alternative to this function, see #Num.raise. -pow : Frac a, Frac a -> Frac a +pow : Float a, Float a -> Float a ## Raises an integer to the power of another, by multiplying the integer by ## itself the given number of times. ## ## This process is known as [exponentiation by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring). ## -## For a #Frac alternative to this function, which supports negative exponents, +## For a [Float] alternative to this function, which supports negative exponents, ## see #Num.exp. ## ## >>> Num.exp 5 0 @@ -855,9 +942,9 @@ pow : Frac a, Frac a -> Frac a ## overflow expBySquaring : Int a, U8 -> Int a -## Returns an approximation of the absolute value of a [Frac]'s square root. +## Returns an approximation of the absolute value of a [Float]'s square root. ## -## The square root of a negative number is an irrational number, and [Frac] only +## The square root of a negative number is an irrational number, and [Float] only ## supports rational numbers. As such, you should make sure never to pass this ## function a negative number! Calling [sqrt] on a negative [Dec] will cause a panic. ## @@ -881,7 +968,7 @@ expBySquaring : Int a, U8 -> Int a ## >>> Frac.sqrt -4.0f64 ## ## >>> Frac.sqrt -4.0dec -sqrt : Frac a -> Frac a +sqrt : Float a -> Float a ## Bit shifts @@ -909,23 +996,22 @@ shr : Int a, Int a -> Int a ## the beginning. (In contrast, [shr] replaces discarded bits with zeroes.) shrWrap : Int a, Int a -> Int a - ## [Endianness](https://en.wikipedia.org/wiki/Endianness) -Endi : [ Big, Little ] +# Endi : [ Big, Little, Native ] -## The [Endi] argument does not matter for [U8] and [I8], since they have +## The `Endi` argument does not matter for [U8] and [I8], since they have ## only one byte. -toBytes : Num *, Endi -> List U8 +# toBytes : Num *, Endi -> List U8 ## when Num.parseBytes bytes Big is ## Ok { val: f64, rest } -> ... ## Err (ExpectedNum (Float Binary64)) -> ... -parseBytes : List U8, Endi -> Result { val : Num a, rest : List U8 } [ ExpectedNum a ]* +# parseBytes : List U8, Endi -> Result { val : Num a, rest : List U8 } [ ExpectedNum a ]* ## when Num.fromBytes bytes Big is ## Ok f64 -> ... ## Err (ExpectedNum (Float Binary64)) -> ... -fromBytes : List U8, Endi -> Result (Num a) [ ExpectedNum a ]* +# fromBytes : List U8, Endi -> Result (Num a) [ ExpectedNum a ]* ## Comparison @@ -992,8 +1078,8 @@ lower : Num a, Num a -> Num a ## Returns `Lt` if the first number is less than the second, `Gt` if ## the first is greater than the second, and `Eq` if they're equal. ## -## Although this can be passed to [List.sort], you'll get better performance -## by using [List.sortAsc] or [List.sortDesc] instead. +## Although this can be passed to `List.sort`, you'll get better performance +## by using `List.sortAsc` or `List.sortDesc` instead. compare : Num a, Num a -> [ Lt, Eq, Gt ] ## Special Floating-Point Values @@ -1005,7 +1091,7 @@ compare : Num a, Num a -> [ Lt, Eq, Gt ] ## ## This is the opposite of [isInfinite], except when given [*NaN*](Num.isNaN). Both ## [isFinite] and [isInfinite] return `False` for [*NaN*](Num.isNaN). -isFinite : Frac * -> Bool +isFinite : Float * -> Bool ## When given a [F64] or [F32] value, returns `True` if that value is either ## ∞ or -∞, and `False` otherwise. @@ -1014,7 +1100,7 @@ isFinite : Frac * -> Bool ## ## This is the opposite of [isFinite], except when given [*NaN*](Num.isNaN). Both ## [isFinite] and [isInfinite] return `False` for [*NaN*](Num.isNaN). -isInfinite : Frac * -> Bool +isInfinite : Float * -> Bool ## When given a [F64] or [F32] value, returns `True` if that value is ## *NaN* ([not a number](https://en.wikipedia.org/wiki/NaN)), and `False` otherwise. @@ -1038,4 +1124,4 @@ isInfinite : Frac * -> Bool ## Note that you should never put a *NaN* into a [Set], or use it as the key in ## a [Dict]. The result is entries that can never be removed from those ## collections! See the documentation for [Set.add] and [Dict.insert] for details. -isNaN : Frac * -> Bool +isNaN : Float * -> Bool diff --git a/compiler/builtins/docs/Result.roc b/compiler/builtins/docs/Result.roc new file mode 100644 index 0000000000..df56b27e66 --- /dev/null +++ b/compiler/builtins/docs/Result.roc @@ -0,0 +1,55 @@ +interface Result + exposes + [ + Result, + map, + mapErr, + withDefault, + after + ] + imports [] + +## The result of an operation that could fail: either the operation went +## okay, or else there was an error of some sort. +Result ok err : [ @Result ok err ] + +## If the result is `Ok`, return the value it holds. Otherwise, return +## the given default value. +## +## >>> Result.withDefault (Ok 7) 42 +## +## >>> Result.withDefault (Err "uh oh") 42 +withDefault : Result ok err, ok -> ok + +## If the result is `Ok`, transform the entire result by running a conversion +## function on the value the `Ok` holds. Then return that new result. +## +## (If the result is `Err`, this has no effect. Use `afterErr` to transform an `Err`.) +## +## >>> Result.after (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num +## +## >>> Result.after (Err "yipes!") \num -> if num < 0 then Err "negative!" else Ok -num +after : Result before err, (before -> Result after err) -> Result after err + +## If the result is `Ok`, transform the value it holds by running a conversion +## function on it. Then return a new `Ok` holding the transformed value. +## +## (If the result is `Err`, this has no effect. Use [mapErr] to transform an `Err`.) +## +## >>> Result.map (Ok 12) Num.negate +## +## >>> Result.map (Err "yipes!") Num.negate +## +## `map` functions like this are common in Roc, and they all work similarly. +## See for example [List.map], `Set.map`, and `Dict.map`. +map : Result before err, (before -> after) -> Result after err + +## If the result is `Err`, transform the value it holds by running a conversion +## function on it. Then return a new `Err` holding the transformed value. +## +## (If the result is `Ok`, this has no effect. Use [map] to transform an `Ok`.) +## +## >>> Result.mapErr (Err "yipes!") Str.isEmpty +## +## >>> Result.mapErr (Ok 12) Str.isEmpty +mapErr : Result ok before, (before -> after) -> Result ok after diff --git a/compiler/builtins/docs/Set.roc b/compiler/builtins/docs/Set.roc index 24c29dac0e..eb8e9722d7 100644 --- a/compiler/builtins/docs/Set.roc +++ b/compiler/builtins/docs/Set.roc @@ -1,9 +1,22 @@ interface Set - exposes [ Set, empty, isEmpty, len, add, drop, map ] + exposes + [ + Set, + empty, + single, + len, + insert, + remove, + union, + difference, + intersection, + toList, + fromList, + walk, + contains + ] imports [] -## Set - ## A Set is an unordered collection of unique elements. Set elem : [ @Set elem ] @@ -36,7 +49,7 @@ drop : Set elem, elem -> Set elem ## >>> Set.map {: "", "a", "bc" :} Str.isEmpty ## ## `map` functions like this are common in Roc, and they all work similarly. -## See for example #Result.map, #List.map, and #Map.map. +## See for example [List.map], `Dict.map`, and [Result.map]. # TODO: removed `'` from signature because parser does not support it yet # Original signature: `map : Set 'elem, ('before -> 'after) -> Set 'after` map : Set elem, (before -> after) -> Set after diff --git a/compiler/builtins/docs/Str.roc b/compiler/builtins/docs/Str.roc index 3a5ed0c88e..071c89f97a 100644 --- a/compiler/builtins/docs/Str.roc +++ b/compiler/builtins/docs/Str.roc @@ -2,36 +2,21 @@ interface Str exposes [ Str, - decimal, - split, isEmpty, + append, + concat, + joinWith, + split, + countGraphemes, startsWith, endsWith, - contains, - anyGraphemes, - allGraphemes, - join, - joinWith, - padGraphemesStart, - padGraphemesEnd, - graphemes, - reverseGraphemes, - isCaseInsensitiveEq, - isCaseInsensitiveNeq, - walkGraphemes, - isCapitalized, - isAllUppercase, - isAllLowercase, + fromInt, + fromFloat, + fromUtf8, + Utf8Problem, + Utf8ByteProblem, toUtf8, - toUtf16, - toUtf32, - trim, - walkUtf8, - walkUtf16, - walkUtf32, - walkRevUtf8, - walkRevUtf16, - walkRevUtf32 + startsWithCodePt ] imports [] @@ -63,7 +48,7 @@ interface Str ## programming, and "extended grapheme cluster" is a mouthful, in Roc we use the ## term "grapheme" as a shorthand for the more precise "extended grapheme cluster." ## -## You can get the number of graphemes in a string by calling #Str.countGraphemes on it: +## You can get the number of graphemes in a string by calling [Str.countGraphemes] on it: ## ## Str.countGraphemes "Roc!" ## Str.countGraphemes "折り紙" @@ -126,7 +111,7 @@ interface Str ## potentially change it without breaking existing Roc applications. (UTF-8 ## seems pretty great today, but so did UTF-16 at an earlier point in history.) ## -## This module has functions to can convert a #Str to a #List of raw [code unit](https://unicode.org/glossary/#code_unit) +## This module has functions to can convert a [Str] to a [List] of raw [code unit](https://unicode.org/glossary/#code_unit) ## integers (not to be confused with the [code points](https://unicode.org/glossary/#code_point) ## mentioned earlier) in a particular encoding. If you need encoding-specific functions, ## you should take a look at the [roc/unicode](roc/unicode) package. @@ -137,15 +122,15 @@ Str : [ @Str ] ## Convert -## Convert a #Float to a decimal string, rounding off to the given number of decimal places. +## Convert a [Float] to a decimal string, rounding off to the given number of decimal places. ## -## Since #Float values are imprecise, it's usually best to limit this to the lowest -## number you can choose that will make sense for what you want to display. -## -## If you want to keep all the digits, passing the same float to #Str.num -## will do that. +## If you want to keep all the digits, use [Str.num] instead. decimal : Float *, Nat -> Str + +## Convert a [Num] to a string. +num : Float *, Nat -> Str + ## Split a string around a separator. ## ## >>> Str.split "1,2,3" "," @@ -155,13 +140,13 @@ decimal : Float *, Nat -> Str ## ## >>> Str.split "1,2,3" "" ## -## To split a string into its individual graphemes, use #Str.graphemes +## To split a string into its individual graphemes, use `Str.graphemes` split : Str, Str -> List Str ## Split a string around newlines. ## ## On strings that use `"\n"` for their line endings, this gives the same answer -## as passing `"\n"` to #Str.split. However, on strings that use `"\n\r"` (such +## as passing `"\n"` to [Str.split]. However, on strings that use `"\n\r"` (such ## as [in Windows files](https://en.wikipedia.org/wiki/Newline#History)), this ## will consume the entire `"\n\r"` instead of just the `"\n"`. ## @@ -169,13 +154,13 @@ split : Str, Str -> List Str ## ## >>> Str.lines "Hello, World!\n\rNice to meet you!" ## -## To split a string using a custom separator, use #Str.split. For more advanced +## To split a string using a custom separator, use [Str.split]. For more advanced ## string splitting, use a #Parser. lines : Str, Str -> List Str ## Check -## Returns #True if the string is empty, and #False otherwise. +## Returns `True` if the string is empty, and `False` otherwise. ## ## >>> Str.isEmpty "hi!" ## @@ -192,13 +177,13 @@ startsWith : Str, Str -> Bool ## ## **Performance Note:** This runs slightly faster than [Str.startsWith], so ## if you want to check whether a string begins with something that's representable -## in a single code point, you can use (for example) `Str.startsWithCodePoint '鹏'` -## instead of `Str.startsWithCodePoint "鹏"`. ('鹏' evaluates to the [U32] +## in a single code point, you can use (for example) `Str.startsWithCodePt '鹏'` +## instead of `Str.startsWithCodePt "鹏"`. ('鹏' evaluates to the [U32] ## value `40527`.) This will not work for graphemes which take up multiple code -## points, however; `Str.startsWithCodePoint '👩‍👩‍👦‍👦'` would be a compiler error +## points, however; `Str.startsWithCodePt '👩‍👩‍👦‍👦'` would be a compiler error ## because 👩‍👩‍👦‍👦 takes up multiple code points and cannot be represented as a -## single [U32]. You'd need to use `Str.startsWithCodePoint "🕊"` instead. -startsWithCodePoint : Str, U32 -> Bool +## single [U32]. You'd need to use `Str.startsWithCodePt "🕊"` instead. +startsWithCodePt : Str, U32 -> Bool endsWith : Str, Str -> Bool @@ -255,9 +240,13 @@ padGraphemesEnd : Str, Nat, Str -> Str ## graphemes : Str -> List Str +## Count the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster) +## in the string. +## ## Str.countGraphemes "Roc!" # 4 ## Str.countGraphemes "七巧板" # 3 ## Str.countGraphemes "🕊" # 1 +countGraphemes : Str -> Nat ## Reverse the order of the string's individual graphemes. ## @@ -268,7 +257,7 @@ graphemes : Str -> List Str ## >>> Str.reversegraphemes "Crème Brûlée" reverseGraphemes : Str -> Str -## Returns #True if the two strings are equal when ignoring case. +## Returns `True` if the two strings are equal when ignoring case. ## ## >>> Str.caseInsensitiveEq "hi" "Hi" isCaseInsensitiveEq : Str, Str -> Bool @@ -280,7 +269,7 @@ walkGraphemesUntil : Str, { start: state, step: (state, Str -> [ Continue state, walkGraphemesBackwards : Str, { start: state, step: (state, Str -> state) } -> state walkGraphemesBackwardsUntil : Str, { start: state, step: (state, Str -> [ Continue state, Done state ]) } -> state -## Returns #True if the string begins with an uppercase letter. +## Returns `True` if the string begins with an uppercase letter. ## ## >>> Str.isCapitalized "Hi" ## @@ -305,7 +294,7 @@ walkGraphemesBackwardsUntil : Str, { start: state, step: (state, Str -> [ Contin ## package for functions which capitalize strings. isCapitalized : Str -> Bool -## Returns #True if the string consists entirely of uppercase letters. +## Returns `True` if the string consists entirely of uppercase letters. ## ## >>> Str.isAllUppercase "hi" ## @@ -326,7 +315,7 @@ isCapitalized : Str -> Bool ## >>> Str.isAllUppercase "" isAllUppercase : Str -> Bool -## Returns #True if the string consists entirely of lowercase letters. +## Returns `True` if the string consists entirely of lowercase letters. ## ## >>> Str.isAllLowercase "hi" ## @@ -354,36 +343,36 @@ trim : Str -> Str ## If the given [U32] is a valid [Unicode Scalar Value](http://www.unicode.org/glossary/#unicode_scalar_value), ## return a [Str] containing only that scalar. fromScalar : U32 -> Result Str [ BadScalar ]* -fromCodePoints : List U32 -> Result Str [ BadCodePoint U32 ]* +fromCodePts : List U32 -> Result Str [ BadCodePt U32 ]* fromUtf8 : List U8 -> Result Str [ BadUtf8 ]* ## Create a [Str] from bytes encoded as [UTF-16LE](https://en.wikipedia.org/wiki/UTF-16#Byte-order_encoding_schemes). -fromUtf16Le : List U8 -> Result Str [ BadUtf16Le Endi ]* +# fromUtf16Le : List U8 -> Result Str [ BadUtf16Le Endi ]* -## Create a [Str] from bytes encoded as [UTF-16BE](https://en.wikipedia.org/wiki/UTF-16#Byte-order_encoding_schemes). -fromUtf16Be : List U8 -> Result Str [ BadUtf16Be Endi ]* +# ## Create a [Str] from bytes encoded as [UTF-16BE](https://en.wikipedia.org/wiki/UTF-16#Byte-order_encoding_schemes). +# fromUtf16Be : List U8 -> Result Str [ BadUtf16Be Endi ]* -## Create a [Str] from bytes encoded as UTF-16 with a [Byte Order Mark](https://en.wikipedia.org/wiki/Byte_order_mark). -fromUtf16Bom : List U8 -> Result Str [ BadUtf16 Endi, NoBom ]* +# ## Create a [Str] from bytes encoded as UTF-16 with a [Byte Order Mark](https://en.wikipedia.org/wiki/Byte_order_mark). +# fromUtf16Bom : List U8 -> Result Str [ BadUtf16 Endi, NoBom ]* -## Create a [Str] from bytes encoded as [UTF-32LE](https://web.archive.org/web/20120322145307/http://mail.apps.ietf.org/ietf/charsets/msg01095.html) -fromUtf32Le : List U8 -> Result Str [ BadUtf32Le Endi ]* +# ## Create a [Str] from bytes encoded as [UTF-32LE](https://web.archive.org/web/20120322145307/http://mail.apps.ietf.org/ietf/charsets/msg01095.html) +# fromUtf32Le : List U8 -> Result Str [ BadUtf32Le Endi ]* -## Create a [Str] from bytes encoded as [UTF-32BE](https://web.archive.org/web/20120322145307/http://mail.apps.ietf.org/ietf/charsets/msg01095.html) -fromUtf32Be : List U8 -> Result Str [ BadUtf32Be Endi ]* +# ## Create a [Str] from bytes encoded as [UTF-32BE](https://web.archive.org/web/20120322145307/http://mail.apps.ietf.org/ietf/charsets/msg01095.html) +# fromUtf32Be : List U8 -> Result Str [ BadUtf32Be Endi ]* -## Create a [Str] from bytes encoded as UTF-32 with a [Byte Order Mark](https://en.wikipedia.org/wiki/Byte_order_mark). -fromUtf32Bom : List U8 -> Result Str [ BadUtf32 Endi, NoBom ]* +# ## Create a [Str] from bytes encoded as UTF-32 with a [Byte Order Mark](https://en.wikipedia.org/wiki/Byte_order_mark). +# fromUtf32Bom : List U8 -> Result Str [ BadUtf32 Endi, NoBom ]* -## Convert from UTF-8, substituting the replacement character ("�") for any -## invalid sequences encountered. -fromUtf8Sub : List U8 -> Str -fromUtf16Sub : List U8, Endi -> Str -fromUtf16BomSub : List U8 -> Result Str [ NoBom ]* +# ## Convert from UTF-8, substituting the replacement character ("�") for any +# ## invalid sequences encountered. +# fromUtf8Sub : List U8 -> Str +# fromUtf16Sub : List U8, Endi -> Str +# fromUtf16BomSub : List U8 -> Result Str [ NoBom ]* -## Return a #List of the string's #U8 UTF-8 [code units](https://unicode.org/glossary/#code_unit). -## (To split the string into a #List of smaller #Str values instead of #U8 values, -## see #Str.split and #Str.graphemes.) +## Return a [List] of the string's [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit). +## (To split the string into a [List] of smaller [Str] values instead of [U8] values, +## see [Str.split] and `Str.graphemes`.) ## ## >>> Str.toUtf8 "👩‍👩‍👦‍👦" ## @@ -393,15 +382,15 @@ fromUtf16BomSub : List U8 -> Result Str [ NoBom ]* ## ## >>> Str.toUtf8 "🐦" ## -## For a more flexible function that walks through each of these #U8 code units -## without creating a #List, see #Str.walkUtf8 and #Str.walkRevUtf8. +## For a more flexible function that walks through each of these [U8] code units +## without creating a [List], see `Str.walkUtf8` and `Str.walkRevUtf8`. toUtf8 : Str -> List U8 toUtf16Be : Str -> List U8 toUtf16Le : Str -> List U8 -toUtf16Bom : Str, Endi -> List U8 +# toUtf16Bom : Str, Endi -> List U8 toUtf32Be : Str -> List U8 toUtf32Le : Str -> List U8 -toUtf32Bom : Str, Endi -> List U8 +# toUtf32Bom : Str, Endi -> List U8 # Parsing @@ -417,7 +406,7 @@ parseGrapheme : Str -> Result { val : Str, rest : Str } [ Expected [ Grapheme ]* ## ## If the string does not begin with a valid code point, for example because it was ## empty, return `Err`. -parseCodePoint : Str -> Result { val : U32, rest : Str } [ Expected [ CodePoint ]* Str ]* +parseCodePt : Str -> Result { val : U32, rest : Str } [ Expected [ CodePt ]* Str ]* ## If the first string begins with the second, return whatever comes ## after the second. @@ -425,9 +414,9 @@ chomp : Str, Str -> Result Str [ Expected [ ExactStr Str ]* Str ]* ## If the string begins with a [Unicode code point](http://www.unicode.org/glossary/#code_point) ## equal to the given [U32], return whatever comes after that code point. -chompCodePoint : Str, U32 -> Result Str [ Expected [ ExactCodePoint U32 ]* Str ]* +chompCodePt : Str, U32 -> Result Str [ Expected [ ExactCodePt U32 ]* Str ]* -## If the string represents a valid #U8 number, return that number. +## If the string represents a valid [U8] number, return that number. ## ## For more advanced options, see [parseU8]. toU8 : Str -> Result U8 [ InvalidU8 ]* @@ -475,20 +464,20 @@ toNum : Str -> Result (Num a) [ ExpectedNum a ]* ## If the string begins with `"NaN"`, `"∞"`, and `"-∞"` (which do not represent ## [finite](Num.isFinite) numbers), they will be accepted only when parsing ## [F64] or [F32] numbers, and translated accordingly. -parseNum : Str, NumParseConfig -> Result { val : Num a, rest : Str } [ ExpectedNum a ]* +# parseNum : Str, NumParseConfig -> Result { val : Num a, rest : Str } [ ExpectedNum a ]* ## Notes: ## * You can allow a decimal mark for integers; they'll only parse if the numbers after it are all 0. ## * For `wholeSep`, `Required` has a payload for how many digits (e.g. "required every 3 digits") ## * For `wholeSep`, `Allowed` allows the separator to appear anywhere. -NumParseConfig : - { - base ? [ Decimal, Hexadecimal, Octal, Binary ], - notation ? [ Standard, Scientific, Any ], - decimalMark ? [ Allowed Str, Required Str, Disallowed ], - decimalDigits ? [ Any, AtLeast U16, Exactly U16 ], - wholeDigits ? [ Any, AtLeast U16, Exactly U16 ], - leadingZeroes ? [ Allowed, Disallowed ], - trailingZeroes ? [ Allowed, Disallowed ], - wholeSep ? { mark : Str, policy : [ Allowed, Required U64 ] } - } +# NumParseConfig : +# { +# base ? [ Decimal, Hexadecimal, Octal, Binary ], +# notation ? [ Standard, Scientific, Any ], +# decimalMark ? [ Allowed Str, Required Str, Disallowed ], +# decimalDigits ? [ Any, AtLeast U16, Exactly U16 ], +# wholeDigits ? [ Any, AtLeast U16, Exactly U16 ], +# leadingZeroes ? [ Allowed, Disallowed ], +# trailingZeroes ? [ Allowed, Disallowed ], +# wholeSep ? { mark : Str, policy : [ Allowed, Required U64 ] } +# } diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 4ac7d0f0f3..c7ac45f69c 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -8,6 +8,9 @@ pub const NUM_ACOS: &str = "roc_builtins.num.acos"; pub const NUM_ATAN: &str = "roc_builtins.num.atan"; pub const NUM_IS_FINITE: &str = "roc_builtins.num.is_finite"; pub const NUM_POW_INT: &str = "roc_builtins.num.pow_int"; +pub const NUM_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16"; +pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32"; +pub const NUM_ROUND: &str = "roc_builtins.num.round"; pub const STR_INIT: &str = "roc_builtins.str.init"; pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments"; @@ -16,14 +19,15 @@ pub const STR_JOIN_WITH: &str = "roc_builtins.str.joinWith"; pub const STR_STR_SPLIT_IN_PLACE: &str = "roc_builtins.str.str_split_in_place"; pub const STR_COUNT_GRAPEHEME_CLUSTERS: &str = "roc_builtins.str.count_grapheme_clusters"; pub const STR_STARTS_WITH: &str = "roc_builtins.str.starts_with"; -pub const STR_STARTS_WITH_CODE_POINT: &str = "roc_builtins.str.starts_with_code_point"; +pub const STR_STARTS_WITH_CODE_PT: &str = "roc_builtins.str.starts_with_code_point"; pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with"; pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes"; pub const STR_FROM_INT: &str = "roc_builtins.str.from_int"; pub const STR_FROM_FLOAT: &str = "roc_builtins.str.from_float"; pub const STR_EQUAL: &str = "roc_builtins.str.equal"; -pub const STR_TO_BYTES: &str = "roc_builtins.str.to_bytes"; +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 DICT_HASH: &str = "roc_builtins.dict.hash"; pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str"; @@ -56,6 +60,7 @@ pub const LIST_WALK_BACKWARDS: &str = "roc_builtins.list.walk_backwards"; pub const LIST_CONTAINS: &str = "roc_builtins.list.contains"; pub const LIST_REPEAT: &str = "roc_builtins.list.repeat"; pub const LIST_APPEND: &str = "roc_builtins.list.append"; +pub const LIST_PREPEND: &str = "roc_builtins.list.prepend"; pub const LIST_DROP: &str = "roc_builtins.list.drop"; pub const LIST_SWAP: &str = "roc_builtins.list.swap"; pub const LIST_SINGLE: &str = "roc_builtins.list.single"; @@ -66,3 +71,14 @@ pub const LIST_SORT_WITH: &str = "roc_builtins.list.sort_with"; pub const LIST_CONCAT: &str = "roc_builtins.list.concat"; pub const LIST_SET: &str = "roc_builtins.list.set"; pub const LIST_SET_IN_PLACE: &str = "roc_builtins.list.set_in_place"; + +pub const DEC_FROM_F64: &str = "roc_builtins.dec.from_f64"; +pub const DEC_EQ: &str = "roc_builtins.dec.eq"; +pub const DEC_NEQ: &str = "roc_builtins.dec.neq"; +pub const DEC_NEGATE: &str = "roc_builtins.dec.negate"; +pub const DEC_ADD_WITH_OVERFLOW: &str = "roc_builtins.dec.add_with_overflow"; +pub const DEC_SUB_WITH_OVERFLOW: &str = "roc_builtins.dec.sub_with_overflow"; +pub const DEC_MUL_WITH_OVERFLOW: &str = "roc_builtins.dec.mul_with_overflow"; +pub const DEC_DIV: &str = "roc_builtins.dec.div"; + +pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic"; diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 158f0523b8..b14d1dd435 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -4,11 +4,12 @@ use roc_module::symbol::Symbol; use roc_region::all::Region; use roc_types::builtin_aliases::{ bool_type, dict_type, float_type, i128_type, int_type, list_type, nat_type, num_type, - ordering_type, result_type, set_type, str_type, str_utf8_byte_problem_type, u32_type, u64_type, - u8_type, + ordering_type, result_type, set_type, str_type, str_utf8_byte_problem_type, u16_type, u32_type, + u64_type, u8_type, }; use roc_types::solved_types::SolvedType; use roc_types::subs::VarId; +use roc_types::types::RecordField; use std::collections::HashMap; /// Example: @@ -500,6 +501,32 @@ pub fn types() -> MutMap { Box::new(float_type(flex(TVAR1))), ); + // bytesToU16 : List U8, Nat -> Result U16 [ OutOfBounds ] + { + let position_out_of_bounds = SolvedType::TagUnion( + vec![(TagName::Global("OutOfBounds".into()), vec![])], + Box::new(SolvedType::Wildcard), + ); + add_top_level_function_type!( + Symbol::NUM_BYTES_TO_U16, + vec![list_type(u8_type()), nat_type()], + Box::new(result_type(u16_type(), position_out_of_bounds)), + ); + } + + // bytesToU32 : List U8, Nat -> Result U32 [ OutOfBounds ] + { + let position_out_of_bounds = SolvedType::TagUnion( + vec![(TagName::Global("OutOfBounds".into()), vec![])], + Box::new(SolvedType::Wildcard), + ); + add_top_level_function_type!( + Symbol::NUM_BYTES_TO_U32, + vec![list_type(u8_type()), nat_type()], + Box::new(result_type(u32_type(), position_out_of_bounds)), + ); + } + // Bool module // and : Bool, Bool -> Bool @@ -563,9 +590,9 @@ pub fn types() -> MutMap { Box::new(bool_type()) ); - // startsWithCodePoint : Str, U32 -> Bool + // startsWithCodePt : Str, U32 -> Bool add_top_level_function_type!( - Symbol::STR_STARTS_WITH_CODE_POINT, + Symbol::STR_STARTS_WITH_CODE_PT, vec![str_type(), u32_type()], Box::new(bool_type()) ); @@ -592,24 +619,54 @@ pub fn types() -> MutMap { ); // fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]* - let bad_utf8 = SolvedType::TagUnion( - vec![( - TagName::Global("BadUtf8".into()), - // vec![str_utf8_problem_type()], - vec![str_utf8_byte_problem_type(), nat_type()], - )], - Box::new(SolvedType::Wildcard), - ); + { + let bad_utf8 = SolvedType::TagUnion( + vec![( + TagName::Global("BadUtf8".into()), + vec![str_utf8_byte_problem_type(), nat_type()], + )], + Box::new(SolvedType::Wildcard), + ); - add_top_level_function_type!( - Symbol::STR_FROM_UTF8, - vec![list_type(u8_type())], - Box::new(result_type(str_type(), bad_utf8)), - ); + add_top_level_function_type!( + Symbol::STR_FROM_UTF8, + vec![list_type(u8_type())], + Box::new(result_type(str_type(), bad_utf8)), + ); + } - // toBytes : Str -> List U8 + // fromUtf8Range : List U8 -> Result Str [ BadUtf8 Utf8Problem, OutOfBounds ]* + { + let bad_utf8 = SolvedType::TagUnion( + vec![ + ( + TagName::Global("BadUtf8".into()), + vec![str_utf8_byte_problem_type(), nat_type()], + ), + (TagName::Global("OutOfBounds".into()), vec![]), + ], + Box::new(SolvedType::Wildcard), + ); + + add_top_level_function_type!( + Symbol::STR_FROM_UTF8_RANGE, + vec![ + list_type(u8_type()), + SolvedType::Record { + fields: vec![ + ("start".into(), RecordField::Required(nat_type())), + ("count".into(), RecordField::Required(nat_type())), + ], + ext: Box::new(SolvedType::EmptyRecord), + } + ], + Box::new(result_type(str_type(), bad_utf8)), + ); + } + + // toUtf8 : Str -> List U8 add_top_level_function_type!( - Symbol::STR_TO_BYTES, + Symbol::STR_TO_UTF8, vec![str_type()], Box::new(list_type(u8_type())) ); diff --git a/compiler/can/Cargo.toml b/compiler/can/Cargo.toml index f02486644a..e5f5764a9f 100644 --- a/compiler/can/Cargo.toml +++ b/compiler/can/Cargo.toml @@ -17,7 +17,6 @@ ven_graph = { path = "../../vendor/pathfinding" } 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" [dev-dependencies] pretty_assertions = "0.5.1" diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index a3c8ed95d2..5bade7aeee 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -6,7 +6,7 @@ use roc_module::symbol::Symbol; use roc_parse::ast::{AssignedField, Tag, TypeAnnotation}; use roc_region::all::{Located, Region}; use roc_types::subs::{VarStore, Variable}; -use roc_types::types::{Alias, Problem, RecordField, Type}; +use roc_types::types::{Alias, LambdaSet, Problem, RecordField, Type}; #[derive(Clone, Debug, PartialEq)] pub struct Annotation { @@ -159,7 +159,7 @@ fn can_annotation_help( Err(problem) => { env.problem(roc_problem::can::Problem::RuntimeError(problem)); - return Type::Erroneous(Problem::UnrecognizedIdent(ident.into())); + return Type::Erroneous(Problem::UnrecognizedIdent(ident)); } } } else { @@ -227,14 +227,27 @@ fn can_annotation_help( } // make sure hidden variables are freshly instantiated - for var in alias.lambda_set_variables.iter() { - substitutions.insert(var.into_inner(), Type::Variable(var_store.fresh())); + let mut lambda_set_variables = + Vec::with_capacity(alias.lambda_set_variables.len()); + for typ in alias.lambda_set_variables.iter() { + if let Type::Variable(var) = typ.0 { + let fresh = var_store.fresh(); + substitutions.insert(var, Type::Variable(fresh)); + lambda_set_variables.push(LambdaSet(Type::Variable(fresh))); + } else { + unreachable!("at this point there should be only vars in there"); + } } // instantiate variables actual.substitute(&substitutions); - Type::Alias(symbol, vars, Box::new(actual)) + Type::Alias { + symbol, + type_arguments: vars, + lambda_set_variables, + actual: Box::new(actual), + } } None => { let mut args = Vec::new(); @@ -373,12 +386,18 @@ fn can_annotation_help( introduced_variables.insert_host_exposed_alias(symbol, actual_var); Type::HostExposedAlias { name: symbol, - arguments: vars, + type_arguments: vars, + lambda_set_variables: alias.lambda_set_variables.clone(), actual: Box::new(alias.typ.clone()), actual_var, } } else { - Type::Alias(symbol, vars, Box::new(alias.typ.clone())) + Type::Alias { + symbol, + type_arguments: vars, + lambda_set_variables: alias.lambda_set_variables.clone(), + actual: Box::new(alias.typ.clone()), + } } } _ => { diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index b6d13be1da..d68f8dd85f 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -58,12 +58,13 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option STR_SPLIT => str_split, STR_IS_EMPTY => str_is_empty, STR_STARTS_WITH => str_starts_with, - STR_STARTS_WITH_CODE_POINT => str_starts_with_code_point, + STR_STARTS_WITH_CODE_PT => str_starts_with_code_point, STR_ENDS_WITH => str_ends_with, STR_COUNT_GRAPHEMES => str_count_graphemes, STR_FROM_INT => str_from_int, STR_FROM_UTF8 => str_from_utf8, - STR_TO_BYTES => str_to_bytes, + STR_FROM_UTF8_RANGE => str_from_utf8_range, + STR_TO_UTF8 => str_to_utf8, STR_FROM_FLOAT=> str_from_float, LIST_LEN => list_len, LIST_GET => list_get, @@ -160,6 +161,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_ATAN => num_atan, NUM_ACOS => num_acos, NUM_ASIN => num_asin, + NUM_BYTES_TO_U16 => num_bytes_to_u16, + NUM_BYTES_TO_U32 => num_bytes_to_u32, NUM_MAX_INT => num_max_int, NUM_MIN_INT => num_min_int, NUM_BITWISE_AND => num_bitwise_and, @@ -455,8 +458,7 @@ fn num_add_wrap(symbol: Symbol, var_store: &mut VarStore) -> Def { num_binop(symbol, var_store, LowLevel::NumAddWrap) } -/// Num.addChecked : Num a, Num a -> Result (Num a) [ Overflow ]* -fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { +fn num_overflow_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel) -> Def { let bool_var = var_store.fresh(); let num_var_1 = var_store.fresh(); let num_var_2 = var_store.fresh(); @@ -464,7 +466,7 @@ fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { let ret_var = var_store.fresh(); let record_var = var_store.fresh(); - // let arg_3 = RunLowLevel NumAddChecked arg_1 arg_2 + // let arg_3 = RunLowLevel NumXXXChecked arg_1 arg_2 // // if arg_3.b then // # overflow @@ -517,11 +519,11 @@ fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { ), }; - // arg_3 = RunLowLevel NumAddChecked arg_1 arg_2 + // arg_3 = RunLowLevel NumXXXChecked arg_1 arg_2 let def = crate::def::Def { loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_3)), loc_expr: no_region(RunLowLevel { - op: LowLevel::NumAddChecked, + op: lowlevel, args: vec![ (num_var_1, Var(Symbol::ARG_1)), (num_var_2, Var(Symbol::ARG_2)), @@ -544,6 +546,11 @@ fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// Num.addChecked : Num a, Num a -> Result (Num a) [ Overflow ]* +fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_overflow_checked(symbol, var_store, LowLevel::NumAddChecked) +} + /// Num.sub : Num a, Num a -> Num a fn num_sub(symbol: Symbol, var_store: &mut VarStore) -> Def { num_binop(symbol, var_store, LowLevel::NumSub) @@ -556,91 +563,7 @@ fn num_sub_wrap(symbol: Symbol, var_store: &mut VarStore) -> Def { /// Num.subChecked : Num a, Num a -> Result (Num a) [ Overflow ]* fn num_sub_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { - let bool_var = var_store.fresh(); - let num_var_1 = var_store.fresh(); - let num_var_2 = var_store.fresh(); - let num_var_3 = var_store.fresh(); - let ret_var = var_store.fresh(); - let record_var = var_store.fresh(); - - // let arg_3 = RunLowLevel NumSubChecked arg_1 arg_2 - // - // if arg_3.b then - // # overflow - // Err Overflow - // else - // # all is well - // Ok arg_3.a - - let cont = If { - branch_var: ret_var, - cond_var: bool_var, - branches: vec![( - // if-condition - no_region( - // arg_3.b - Access { - record_var, - ext_var: var_store.fresh(), - field: "b".into(), - field_var: var_store.fresh(), - loc_expr: Box::new(no_region(Var(Symbol::ARG_3))), - }, - ), - // overflow! - no_region(tag( - "Err", - vec![tag("Overflow", Vec::new(), var_store)], - var_store, - )), - )], - final_else: Box::new( - // all is well - no_region( - // Ok arg_3.a - tag( - "Ok", - vec![ - // arg_3.a - Access { - record_var, - ext_var: var_store.fresh(), - field: "a".into(), - field_var: num_var_3, - loc_expr: Box::new(no_region(Var(Symbol::ARG_3))), - }, - ], - var_store, - ), - ), - ), - }; - - // arg_3 = RunLowLevel NumSubChecked arg_1 arg_2 - let def = crate::def::Def { - loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_3)), - loc_expr: no_region(RunLowLevel { - op: LowLevel::NumSubChecked, - args: vec![ - (num_var_1, Var(Symbol::ARG_1)), - (num_var_2, Var(Symbol::ARG_2)), - ], - ret_var: record_var, - }), - expr_var: record_var, - pattern_vars: SendMap::default(), - annotation: None, - }; - - let body = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var); - - defn( - symbol, - vec![(num_var_1, Symbol::ARG_1), (num_var_2, Symbol::ARG_2)], - var_store, - body, - ret_var, - ) + num_overflow_checked(symbol, var_store, LowLevel::NumSubChecked) } /// Num.mul : Num a, Num a -> Num a @@ -655,91 +578,7 @@ fn num_mul_wrap(symbol: Symbol, var_store: &mut VarStore) -> Def { /// Num.mulChecked : Num a, Num a -> Result (Num a) [ Overflow ]* fn num_mul_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { - let bool_var = var_store.fresh(); - let num_var_1 = var_store.fresh(); - let num_var_2 = var_store.fresh(); - let num_var_3 = var_store.fresh(); - let ret_var = var_store.fresh(); - let record_var = var_store.fresh(); - - // let arg_3 = RunLowLevel NumMulChecked arg_1 arg_2 - // - // if arg_3.b then - // # overflow - // Err Overflow - // else - // # all is well - // Ok arg_3.a - - let cont = If { - branch_var: ret_var, - cond_var: bool_var, - branches: vec![( - // if-condition - no_region( - // arg_3.b - Access { - record_var, - ext_var: var_store.fresh(), - field: "b".into(), - field_var: var_store.fresh(), - loc_expr: Box::new(no_region(Var(Symbol::ARG_3))), - }, - ), - // overflow! - no_region(tag( - "Err", - vec![tag("Overflow", Vec::new(), var_store)], - var_store, - )), - )], - final_else: Box::new( - // all is well - no_region( - // Ok arg_3.a - tag( - "Ok", - vec![ - // arg_3.a - Access { - record_var, - ext_var: var_store.fresh(), - field: "a".into(), - field_var: num_var_3, - loc_expr: Box::new(no_region(Var(Symbol::ARG_3))), - }, - ], - var_store, - ), - ), - ), - }; - - // arg_3 = RunLowLevel NumMulChecked arg_1 arg_2 - let def = crate::def::Def { - loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_3)), - loc_expr: no_region(RunLowLevel { - op: LowLevel::NumMulChecked, - args: vec![ - (num_var_1, Var(Symbol::ARG_1)), - (num_var_2, Var(Symbol::ARG_2)), - ], - ret_var: record_var, - }), - expr_var: record_var, - pattern_vars: SendMap::default(), - annotation: None, - }; - - let body = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var); - - defn( - symbol, - vec![(num_var_1, Symbol::ARG_1), (num_var_2, Symbol::ARG_2)], - var_store, - body, - ret_var, - ) + num_overflow_checked(symbol, var_store, LowLevel::NumMulChecked) } /// Num.isGt : Num a, Num a -> Bool @@ -1251,6 +1090,16 @@ fn num_asin(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// Num.bytesToU16 : List U8, Nat -> Result U16 [ OutOfBounds ] +fn num_bytes_to_u16(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_bytes_to(symbol, var_store, 1, LowLevel::NumBytesToU16) +} + +/// Num.bytesToU32 : List U8, Nat -> Result U32 [ OutOfBounds ] +fn num_bytes_to_u32(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_bytes_to(symbol, var_store, 3, LowLevel::NumBytesToU32) +} + /// Num.bitwiseAnd : Int a, Int a -> Int a fn num_bitwise_and(symbol: Symbol, var_store: &mut VarStore) -> Def { num_binop(symbol, var_store, LowLevel::NumBitwiseAnd) @@ -1451,9 +1300,9 @@ fn str_starts_with(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_2(symbol, LowLevel::StrStartsWith, var_store) } -/// Str.startsWithCodePoint : Str, U32 -> Bool +/// Str.startsWithCodePt : Str, U32 -> Bool fn str_starts_with_code_point(symbol: Symbol, var_store: &mut VarStore) -> Def { - lowlevel_2(symbol, LowLevel::StrStartsWithCodePoint, var_store) + lowlevel_2(symbol, LowLevel::StrStartsWithCodePt, var_store) } /// Str.endsWith : Str, Str -> Bool @@ -1516,7 +1365,7 @@ fn str_from_int(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } -/// Str.fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]* +/// Str.fromUtf8 : List U8 -> Result Str [ BadUtf8 { byteIndex : Nat, problem : Utf8Problem } } ]* fn str_from_utf8(symbol: Symbol, var_store: &mut VarStore) -> Def { let bytes_var = var_store.fresh(); let bool_var = var_store.fresh(); @@ -1619,10 +1468,183 @@ fn str_from_utf8(symbol: Symbol, var_store: &mut VarStore) -> Def { ret_var, ) } +/// Str.fromUtf8Range : List U8, { start : Nat, count : Nat } -> Result Str [ BadUtf8 { byteIndex : Nat, problem : Utf8Problem } } ]* +fn str_from_utf8_range(symbol: Symbol, var_store: &mut VarStore) -> Def { + let bytes_var = var_store.fresh(); + let bool_var = var_store.fresh(); + let arg_record_var = var_store.fresh(); + let ll_record_var = var_store.fresh(); + let ret_var = var_store.fresh(); -/// Str.toBytes : Str -> List U8 -fn str_to_bytes(symbol: Symbol, var_store: &mut VarStore) -> Def { - lowlevel_1(symbol, LowLevel::StrToBytes, var_store) + // let arg_3 = RunLowLevel FromUtf8Range arg_1 arg_2 + // + // arg_3 : + // { a : Bool -- isOk + // , b : String -- result_str + // , c : Nat -- problem_byte_index + // , d : I8 -- problem_code + // } + // + // if arg_3.a then + // Ok arg_3.str + // else + // Err (BadUtf8 { byteIndex: arg_3.byteIndex, problem : arg_3.problem }) + + let def = crate::def::Def { + loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_3)), + loc_expr: no_region(RunLowLevel { + op: LowLevel::StrFromUtf8Range, + args: vec![ + (bytes_var, Var(Symbol::ARG_1)), + (arg_record_var, Var(Symbol::ARG_2)), + ], + ret_var: ll_record_var, + }), + expr_var: ll_record_var, + pattern_vars: SendMap::default(), + annotation: None, + }; + + let cont = If { + branch_var: ret_var, + cond_var: bool_var, + branches: vec![( + // if-condition + no_region( + // arg_2.c -> Bool + Access { + record_var: ll_record_var, + ext_var: var_store.fresh(), + field: "c_isOk".into(), + field_var: var_store.fresh(), + loc_expr: Box::new(no_region(Var(Symbol::ARG_3))), + }, + ), + // all is good + no_region(tag( + "Ok", + // arg_2.a -> Str + vec![Access { + record_var: ll_record_var, + ext_var: var_store.fresh(), + field: "b_str".into(), + field_var: var_store.fresh(), + loc_expr: Box::new(no_region(Var(Symbol::ARG_3))), + }], + var_store, + )), + )], + final_else: Box::new( + // bad!! + no_region(tag( + "Err", + vec![tag( + "BadUtf8", + vec![ + Access { + record_var: ll_record_var, + ext_var: var_store.fresh(), + field: "d_problem".into(), + field_var: var_store.fresh(), + loc_expr: Box::new(no_region(Var(Symbol::ARG_3))), + }, + Access { + record_var: ll_record_var, + ext_var: var_store.fresh(), + field: "a_byteIndex".into(), + field_var: var_store.fresh(), + loc_expr: Box::new(no_region(Var(Symbol::ARG_3))), + }, + ], + var_store, + )], + var_store, + )), + ), + }; + + let roc_result = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var); + + // Only do the business with the let if we're in bounds! + + let bounds_var = var_store.fresh(); + let bounds_bool = var_store.fresh(); + let add_var = var_store.fresh(); + + let body = If { + cond_var: bounds_bool, + branch_var: ret_var, + branches: vec![( + no_region(RunLowLevel { + op: LowLevel::NumLte, + args: vec![ + ( + bounds_var, + RunLowLevel { + op: LowLevel::NumAdd, + args: vec![ + ( + add_var, + Access { + record_var: arg_record_var, + ext_var: var_store.fresh(), + field: "start".into(), + field_var: var_store.fresh(), + loc_expr: Box::new(no_region(Var(Symbol::ARG_2))), + }, + ), + ( + add_var, + Access { + record_var: arg_record_var, + ext_var: var_store.fresh(), + field: "count".into(), + field_var: var_store.fresh(), + loc_expr: Box::new(no_region(Var(Symbol::ARG_2))), + }, + ), + ], + ret_var: add_var, + }, + ), + ( + bounds_var, + RunLowLevel { + op: LowLevel::ListLen, + args: vec![(bytes_var, Var(Symbol::ARG_1))], + ret_var: bounds_var, + }, + ), + ], + ret_var: bounds_bool, + }), + no_region(roc_result), + )], + final_else: Box::new( + // else-branch + no_region( + // Err + tag( + "Err", + vec![tag("OutOfBounds", Vec::new(), var_store)], + var_store, + ), + ), + ), + }; + + defn( + symbol, + vec![(bytes_var, Symbol::ARG_1), (arg_record_var, Symbol::ARG_2)], + var_store, + body, + ret_var, + ) +} + +/// Str.toUtf8 : Str -> List U8 +fn str_to_utf8(symbol: Symbol, var_store: &mut VarStore) -> Def { + lowlevel_1(symbol, LowLevel::StrToUtf8, var_store) } /// Str.fromFloat : Float * -> Str @@ -3349,6 +3371,97 @@ fn defn( } } +#[inline(always)] +fn num_bytes_to(symbol: Symbol, var_store: &mut VarStore, offset: i64, low_level: LowLevel) -> Def { + let len_var = var_store.fresh(); + let list_var = var_store.fresh(); + let elem_var = var_store.fresh(); + + let ret_var = var_store.fresh(); + let bool_var = var_store.fresh(); + let add_var = var_store.fresh(); + let cast_var = var_store.fresh(); + + // Perform a bounds check. If it passes, run LowLevel::low_level + let body = If { + cond_var: bool_var, + branch_var: var_store.fresh(), + branches: vec![( + // if-condition + no_region( + // index + offset < List.len list + RunLowLevel { + op: LowLevel::NumLt, + args: vec![ + ( + len_var, + RunLowLevel { + op: LowLevel::NumAdd, + args: vec![ + (add_var, Var(Symbol::ARG_2)), + ( + add_var, + RunLowLevel { + ret_var: cast_var, + args: vec![(cast_var, Num(var_store.fresh(), offset))], + op: LowLevel::NumIntCast, + }, + ), + ], + ret_var: add_var, + }, + ), + ( + len_var, + RunLowLevel { + op: LowLevel::ListLen, + args: vec![(list_var, Var(Symbol::ARG_1))], + ret_var: len_var, + }, + ), + ], + ret_var: bool_var, + }, + ), + // then-branch + no_region( + // Ok + tag( + "Ok", + vec![RunLowLevel { + op: low_level, + args: vec![ + (list_var, Var(Symbol::ARG_1)), + (len_var, Var(Symbol::ARG_2)), + ], + ret_var: elem_var, + }], + var_store, + ), + ), + )], + final_else: Box::new( + // else-branch + no_region( + // Err + tag( + "Err", + vec![tag("OutOfBounds", Vec::new(), var_store)], + var_store, + ), + ), + ), + }; + + defn( + symbol, + vec![(list_var, Symbol::ARG_1), (len_var, Symbol::ARG_2)], + var_store, + body, + ret_var, + ) +} + #[inline(always)] fn defn_help( fn_name: Symbol, diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index dfac99f49e..a300d48ea8 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -60,33 +60,49 @@ impl Constraint { true } + + pub fn contains_save_the_environment(&self) -> bool { + match self { + Constraint::Eq(_, _, _, _) => false, + Constraint::Store(_, _, _, _) => false, + Constraint::Lookup(_, _, _) => false, + Constraint::Pattern(_, _, _, _) => false, + Constraint::True => false, + Constraint::SaveTheEnvironment => true, + Constraint::Let(boxed) => { + boxed.ret_constraint.contains_save_the_environment() + || boxed.defs_constraint.contains_save_the_environment() + } + Constraint::And(cs) => cs.iter().any(|c| c.contains_save_the_environment()), + } + } } fn subtract(declared: &Declared, detail: &VariableDetail, accum: &mut VariableDetail) { for var in &detail.type_variables { - if !(declared.rigid_vars.contains(&var) || declared.flex_vars.contains(&var)) { + if !(declared.rigid_vars.contains(var) || declared.flex_vars.contains(var)) { accum.type_variables.insert(*var); } } // lambda set variables are always flex for var in &detail.lambda_set_variables { - if declared.rigid_vars.contains(&var.into_inner()) { + if declared.rigid_vars.contains(var) { panic!("lambda set variable {:?} is declared as rigid", var); } - if !declared.flex_vars.contains(&var.into_inner()) { - accum.lambda_set_variables.insert(*var); + if !declared.flex_vars.contains(var) { + accum.lambda_set_variables.push(*var); } } // recursion vars should be always rigid for var in &detail.recursion_variables { - if declared.flex_vars.contains(&var) { + if declared.flex_vars.contains(var) { panic!("recursion variable {:?} is declared as flex", var); } - if !declared.rigid_vars.contains(&var) { + if !declared.rigid_vars.contains(var) { accum.recursion_variables.insert(*var); } } diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index b4a4652924..35f967e83a 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -380,7 +380,7 @@ pub fn sort_can_defs( // // In the above example, `f` cannot reference `a`, and in the closure // a call to `f` cannot cycle back to `a`. - let mut loc_succ = local_successors(&references, &env.closures); + let mut loc_succ = local_successors(references, &env.closures); // if the current symbol is a closure, peek into its body if let Some(References { lookups, .. }) = env.closures.get(symbol) { @@ -430,7 +430,7 @@ pub fn sort_can_defs( // // In the above example, `f` cannot reference `a`, and in the closure // a call to `f` cannot cycle back to `a`. - let mut loc_succ = local_successors(&references, &env.closures); + let mut loc_succ = local_successors(references, &env.closures); // if the current symbol is a closure, peek into its body if let Some(References { lookups, .. }) = env.closures.get(symbol) { @@ -454,7 +454,7 @@ pub fn sort_can_defs( let direct_successors = |symbol: &Symbol| -> ImSet { match refs_by_symbol.get(symbol) { Some((_, references)) => { - let mut loc_succ = local_successors(&references, &env.closures); + let mut loc_succ = local_successors(references, &env.closures); // NOTE: if the symbol is a closure we DONT look into its body @@ -540,7 +540,7 @@ pub fn sort_can_defs( ), Some((region, _)) => { let expr_region = - can_defs_by_symbol.get(&symbol).unwrap().loc_expr.region; + can_defs_by_symbol.get(symbol).unwrap().loc_expr.region; let entry = CycleEntry { symbol: *symbol, @@ -662,11 +662,11 @@ fn group_to_declaration( // for a definition, so every definition is only inserted (thus typechecked and emitted) once let mut seen_pattern_regions: ImSet = ImSet::default(); - for cycle in strongly_connected_components(&group, filtered_successors) { + for cycle in strongly_connected_components(group, filtered_successors) { if cycle.len() == 1 { let symbol = &cycle[0]; - if let Some(can_def) = can_defs_by_symbol.get(&symbol) { + if let Some(can_def) = can_defs_by_symbol.get(symbol) { let mut new_def = can_def.clone(); // Determine recursivity of closures that are not tail-recursive @@ -678,7 +678,7 @@ fn group_to_declaration( *recursive = closure_recursivity(*symbol, closures); } - let is_recursive = successors(&symbol).contains(&symbol); + let is_recursive = successors(symbol).contains(symbol); if !seen_pattern_regions.contains(&new_def.loc_pattern.region) { if is_recursive { @@ -854,7 +854,7 @@ fn canonicalize_pending_def<'a>( }; for (_, (symbol, _)) in scope.idents() { - if !vars_by_symbol.contains_key(&symbol) { + if !vars_by_symbol.contains_key(symbol) { continue; } @@ -999,7 +999,7 @@ fn canonicalize_pending_def<'a>( // // Only defs of the form (foo = ...) can be closure declarations or self tail calls. if let ( - &ast::Pattern::Identifier(ref _name), + &ast::Pattern::Identifier(_name), &Pattern::Identifier(ref defined_symbol), &Closure { function_type, @@ -1021,7 +1021,7 @@ fn canonicalize_pending_def<'a>( // Since everywhere in the code it'll be referred to by its defined name, // remove its generated name from the closure map. (We'll re-insert it later.) - let references = env.closures.remove(&symbol).unwrap_or_else(|| { + let references = env.closures.remove(symbol).unwrap_or_else(|| { panic!( "Tried to remove symbol {:?} from procedures, but it was not found: {:?}", symbol, env.closures @@ -1065,7 +1065,7 @@ fn canonicalize_pending_def<'a>( // Store the referenced locals in the refs_by_symbol map, so we can later figure out // which defined names reference each other. for (_, (symbol, region)) in scope.idents() { - if !vars_by_symbol.contains_key(&symbol) { + if !vars_by_symbol.contains_key(symbol) { continue; } @@ -1110,10 +1110,8 @@ fn canonicalize_pending_def<'a>( // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. let outer_identifier = env.tailcallable_symbol; - if let ( - &ast::Pattern::Identifier(ref _name), - &Pattern::Identifier(ref defined_symbol), - ) = (&loc_pattern.value, &loc_can_pattern.value) + if let (&ast::Pattern::Identifier(_name), &Pattern::Identifier(ref defined_symbol)) = + (&loc_pattern.value, &loc_can_pattern.value) { env.tailcallable_symbol = Some(*defined_symbol); @@ -1144,7 +1142,7 @@ fn canonicalize_pending_def<'a>( // // Only defs of the form (foo = ...) can be closure declarations or self tail calls. if let ( - &ast::Pattern::Identifier(ref _name), + &ast::Pattern::Identifier(_name), &Pattern::Identifier(ref defined_symbol), &Closure { function_type, @@ -1166,7 +1164,7 @@ fn canonicalize_pending_def<'a>( // Since everywhere in the code it'll be referred to by its defined name, // remove its generated name from the closure map. (We'll re-insert it later.) - let references = env.closures.remove(&symbol).unwrap_or_else(|| { + let references = env.closures.remove(symbol).unwrap_or_else(|| { panic!( "Tried to remove symbol {:?} from procedures, but it was not found: {:?}", symbol, env.closures @@ -1555,7 +1553,7 @@ fn correct_mutual_recursive_type_alias<'a>( let mut loc_succ = alias.typ.symbols(); // remove anything that is not defined in the current block loc_succ.retain(|key| symbols_introduced.contains(key)); - loc_succ.remove(&symbol); + loc_succ.remove(symbol); loc_succ } @@ -1634,7 +1632,7 @@ fn make_tag_union_recursive<'a>( typ.substitute_alias(symbol, &Type::Variable(rec_var)); } Type::RecursiveTagUnion(_, _, _) => {} - Type::Alias(_, _, actual) => make_tag_union_recursive( + Type::Alias { actual, .. } => make_tag_union_recursive( env, symbol, region, diff --git a/compiler/can/src/env.rs b/compiler/can/src/env.rs index b86b3f54bf..3054193b65 100644 --- a/compiler/can/src/env.rs +++ b/compiler/can/src/env.rs @@ -1,7 +1,6 @@ use crate::procedure::References; -use inlinable_string::InlinableString; use roc_collections::all::{MutMap, MutSet}; -use roc_module::ident::ModuleName; +use roc_module::ident::{Ident, ModuleName}; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Located, Region}; @@ -12,7 +11,7 @@ pub struct Env<'a> { /// are assumed to be relative to this path. pub home: ModuleId, - pub dep_idents: MutMap, + pub dep_idents: &'a MutMap, pub module_ids: &'a ModuleIds, @@ -40,7 +39,7 @@ pub struct Env<'a> { impl<'a> Env<'a> { pub fn new( home: ModuleId, - dep_idents: MutMap, + dep_idents: &'a MutMap, module_ids: &'a ModuleIds, exposed_ident_ids: IdentIds, ) -> Env<'a> { @@ -62,22 +61,21 @@ impl<'a> Env<'a> { /// Returns Err if the symbol resolved, but it was not exposed by the given module pub fn qualified_lookup( &mut self, - module_name: &str, + module_name_str: &str, ident: &str, region: Region, ) -> Result { debug_assert!( - !module_name.is_empty(), + !module_name_str.is_empty(), "Called env.qualified_lookup with an unqualified ident: {:?}", ident ); - let module_name: InlinableString = module_name.into(); + let module_name = ModuleName::from(module_name_str); + let ident = Ident::from(ident); match self.module_ids.get_id(&module_name) { Some(&module_id) => { - let ident: InlinableString = ident.into(); - // You can do qualified lookups on your own module, e.g. // if I'm in the Foo module, I can do a `Foo.bar` lookup. if module_id == self.home { @@ -114,7 +112,7 @@ impl<'a> Env<'a> { Ok(symbol) } None => Err(RuntimeError::ValueNotExposed { - module_name: ModuleName::from(module_name), + module_name, ident, region, }), diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 4116a51128..264e6fefe6 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -9,7 +9,6 @@ use crate::num::{ use crate::pattern::{canonicalize_pattern, Pattern}; use crate::procedure::References; use crate::scope::Scope; -use inlinable_string::InlinableString; use roc_collections::all::{ImSet, MutMap, MutSet, SendMap}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; @@ -58,7 +57,7 @@ pub enum Expr { // Int and Float store a variable to generate better error messages Int(Variable, Variable, i128), Float(Variable, Variable, f64), - Str(InlinableString), + Str(Box), List { elem_var: Variable, loc_elems: Vec>, @@ -980,7 +979,7 @@ where visited.insert(defined_symbol); for local in refs.lookups.iter() { - if !visited.contains(&local) { + if !visited.contains(local) { let other_refs: References = references_from_local(*local, visited, refs_by_def, closures); @@ -991,7 +990,7 @@ where } for call in refs.calls.iter() { - if !visited.contains(&call) { + if !visited.contains(call) { let other_refs = references_from_call(*call, visited, refs_by_def, closures); answer = answer.union(other_refs); @@ -1022,7 +1021,7 @@ where visited.insert(call_symbol); for closed_over_local in references.lookups.iter() { - if !visited.contains(&closed_over_local) { + if !visited.contains(closed_over_local) { let other_refs = references_from_local(*closed_over_local, visited, refs_by_def, closures); @@ -1033,7 +1032,7 @@ where } for call in references.calls.iter() { - if !visited.contains(&call) { + if !visited.contains(call) { let other_refs = references_from_call(*call, visited, refs_by_def, closures); answer = answer.union(other_refs); @@ -1574,7 +1573,7 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool { enum StrSegment { Interpolation(Located), - Plaintext(InlinableString), + Plaintext(Box), } fn flatten_str_lines<'a>( @@ -1601,10 +1600,10 @@ fn flatten_str_lines<'a>( buf.push(ch); } None => { - env.problem(Problem::InvalidUnicodeCodePoint(loc_hex_digits.region)); + env.problem(Problem::InvalidUnicodeCodePt(loc_hex_digits.region)); return ( - Expr::RuntimeError(RuntimeError::InvalidUnicodeCodePoint( + Expr::RuntimeError(RuntimeError::InvalidUnicodeCodePt( loc_hex_digits.region, )), output, diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 345bb0853e..fb5d2e8b3b 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -47,7 +47,7 @@ pub fn canonicalize_module_defs<'a, F>( home: ModuleId, module_ids: &ModuleIds, exposed_ident_ids: IdentIds, - dep_idents: MutMap, + dep_idents: &'a MutMap, aliases: MutMap, exposed_imports: MutMap, exposed_symbols: &MutSet, @@ -98,7 +98,7 @@ where // Here we essentially add those "defs" to "the beginning of the module" // by canonicalizing them right before we canonicalize the actual ast::Def nodes. for (ident, (symbol, region)) in exposed_imports { - let first_char = ident.as_inline_str().chars().next().unwrap(); + let first_char = ident.as_inline_str().as_str().chars().next().unwrap(); if first_char.is_lowercase() { // this is a value definition @@ -139,7 +139,7 @@ where } } - let (defs, _scope, output, symbols_introduced) = canonicalize_defs( + let (defs, scope, output, symbols_introduced) = canonicalize_defs( &mut env, Output::default(), var_store, diff --git a/compiler/can/src/operator.rs b/compiler/can/src/operator.rs index 4b6fbf14f2..36b790764d 100644 --- a/compiler/can/src/operator.rs +++ b/compiler/can/src/operator.rs @@ -276,7 +276,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a }) } When(loc_cond_expr, branches) => { - let loc_desugared_cond = &*arena.alloc(desugar_expr(arena, &loc_cond_expr)); + let loc_desugared_cond = &*arena.alloc(desugar_expr(arena, loc_cond_expr)); let mut desugared_branches = Vec::with_capacity_in(branches.len(), arena); for branch in branches.iter() { @@ -346,7 +346,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a } If(if_thens, final_else_branch) => { // If does not get desugared into `when` so we can give more targeted error messages during type checking. - let desugared_final_else = &*arena.alloc(desugar_expr(arena, &final_else_branch)); + let desugared_final_else = &*arena.alloc(desugar_expr(arena, final_else_branch)); let mut desugared_if_thens = Vec::with_capacity_in(if_thens.len(), arena); @@ -363,8 +363,8 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a }) } Expect(condition, continuation) => { - let desugared_condition = &*arena.alloc(desugar_expr(arena, &condition)); - let desugared_continuation = &*arena.alloc(desugar_expr(arena, &continuation)); + let desugared_condition = &*arena.alloc(desugar_expr(arena, condition)); + let desugared_continuation = &*arena.alloc(desugar_expr(arena, continuation)); arena.alloc(Located { value: Expect(desugared_condition, desugared_continuation), region: loc_expr.region, diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index 28ea70f9e5..92b1ca611b 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -185,7 +185,7 @@ pub fn canonicalize_pattern<'a>( } } - FloatLiteral(ref string) => match pattern_type { + FloatLiteral(string) => match pattern_type { WhenBranch => match finish_parsing_float(string) { Err(_error) => { let problem = MalformedPatternProblem::MalformedFloat; diff --git a/compiler/can/src/scope.rs b/compiler/can/src/scope.rs index 9037d6e193..bde1032119 100644 --- a/compiler/can/src/scope.rs +++ b/compiler/can/src/scope.rs @@ -47,7 +47,7 @@ impl Scope { let alias = Alias { region, typ, - lambda_set_variables: MutSet::default(), + lambda_set_variables: Vec::new(), recursion_variables: MutSet::default(), type_variables: variables, }; @@ -89,7 +89,7 @@ impl Scope { None => Err(RuntimeError::LookupNotInScope( Located { region, - value: ident.clone().into(), + value: ident.clone(), }, self.idents.keys().map(|v| v.as_ref().into()).collect(), )), @@ -124,9 +124,9 @@ impl Scope { // If this IdentId was already added previously // when the value was exposed in the module header, // use that existing IdentId. Otherwise, create a fresh one. - let ident_id = match exposed_ident_ids.get_id(&ident.as_inline_str()) { + let ident_id = match exposed_ident_ids.get_id(&ident) { Some(ident_id) => *ident_id, - None => all_ident_ids.add(ident.clone().into()), + None => all_ident_ids.add(ident.clone()), }; let symbol = Symbol::new(self.home, ident_id); @@ -143,7 +143,7 @@ impl Scope { /// /// Used for record guards like { x: Just _ } pub fn ignore(&mut self, ident: Ident, all_ident_ids: &mut IdentIds) -> Symbol { - let ident_id = all_ident_ids.add(ident.into()); + let ident_id = all_ident_ids.add(ident); Symbol::new(self.home, ident_id) } @@ -198,6 +198,11 @@ impl Scope { true }); + let lambda_set_variables: Vec<_> = lambda_set_variables + .into_iter() + .map(|v| roc_types::types::LambdaSet(Type::Variable(v))) + .collect(); + let alias = Alias { region, type_variables: vars, diff --git a/compiler/can/src/string.rs b/compiler/can/src/string.rs index 5911ce1a3a..1baef3ee3b 100644 --- a/compiler/can/src/string.rs +++ b/compiler/can/src/string.rs @@ -313,7 +313,7 @@ pub fn canonical_string_literal<'a>(_arena: &Bump, _raw: &'a str, _region: Regio // problems.push(Loc { // region, -// value: Problem::UnicodeCodePointTooLarge, +// value: Problem::UnicodeCodePtTooLarge, // }); // } else { // // If it all checked out, add it to @@ -322,7 +322,7 @@ pub fn canonical_string_literal<'a>(_arena: &Bump, _raw: &'a str, _region: Regio // Some(ch) => buf.push(ch), // None => { // problems.push(loc_escaped_unicode( -// Problem::InvalidUnicodeCodePoint, +// Problem::InvalidUnicodeCodePt, // &state, // start_of_unicode, // hex_str.len(), @@ -335,7 +335,7 @@ pub fn canonical_string_literal<'a>(_arena: &Bump, _raw: &'a str, _region: Regio // let problem = if hex_str.is_empty() { // Problem::NoUnicodeDigits // } else { -// Problem::NonHexCharsInUnicodeCodePoint +// Problem::NonHexCharsInUnicodeCodePt // }; // problems.push(loc_escaped_unicode( diff --git a/compiler/can/tests/helpers/mod.rs b/compiler/can/tests/helpers/mod.rs index 8540427d98..2aff0cf76a 100644 --- a/compiler/can/tests/helpers/mod.rs +++ b/compiler/can/tests/helpers/mod.rs @@ -34,7 +34,7 @@ pub struct CanExprOut { #[allow(dead_code)] pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut { - let loc_expr = roc_parse::test_helpers::parse_loc_with(&arena, expr_str).unwrap_or_else(|e| { + let loc_expr = roc_parse::test_helpers::parse_loc_with(arena, expr_str).unwrap_or_else(|e| { panic!( "can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}", expr_str, e @@ -56,7 +56,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut let mut scope = Scope::new(home, &mut var_store); let dep_idents = IdentIds::exposed_builtins(0); - let mut env = Env::new(home, dep_idents, &module_ids, IdentIds::default()); + let mut env = Env::new(home, &dep_idents, &module_ids, IdentIds::default()); let (loc_expr, output) = canonicalize_expr( &mut env, &mut var_store, diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index 68d3139c25..a937af1c45 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -145,7 +145,7 @@ mod test_can { let region = Region::zero(); assert_can( - &string.clone(), + string.clone(), RuntimeError(RuntimeError::InvalidFloat( FloatErrorKind::Error, region, @@ -658,7 +658,7 @@ mod test_can { recursive: recursion, .. }) => recursion.clone(), - Some(other @ _) => { + Some(other) => { panic!("assignment at {} is not a closure, but a {:?}", i, other) } None => { @@ -680,7 +680,7 @@ mod test_can { recursive: recursion, .. } => recursion.clone(), - other @ _ => { + other => { panic!("assignment at {} is not a closure, but a {:?}", i, other) } } @@ -1590,7 +1590,7 @@ mod test_can { // // (Rust has this restriction. I assume it's a good idea.) // assert_malformed_str( // r#""abc\u{110000}def""#, - // vec![Located::new(0, 7, 0, 12, Problem::UnicodeCodePointTooLarge)], + // vec![Located::new(0, 7, 0, 12, Problem::UnicodeCodePtTooLarge)], // ); // } diff --git a/compiler/collections/src/all.rs b/compiler/collections/src/all.rs index f473ef4e97..efd74751bb 100644 --- a/compiler/collections/src/all.rs +++ b/compiler/collections/src/all.rs @@ -79,7 +79,7 @@ where let mut buf = String::new_in(arena); if let Some(first) = strings.next() { - buf.push_str(&first); + buf.push_str(first); for string in strings { buf.reserve(join_str.len() + string.len()); @@ -133,7 +133,7 @@ where let mut answer = MutMap::default(); for (key, right_value) in map2 { - match std::collections::HashMap::get(map1, &key) { + match std::collections::HashMap::get(map1, key) { None => (), Some(left_value) => { answer.insert(key.clone(), (left_value.clone(), right_value.clone())); diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 4704c67539..c47a1e5796 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -2,7 +2,7 @@ use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::LetConstraint; use roc_can::expected::Expected::{self, *}; use roc_collections::all::SendMap; -use roc_module::ident::TagName; +use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_region::all::Region; use roc_types::subs::Variable; @@ -89,9 +89,23 @@ pub fn str_type() -> Type { builtin_type(Symbol::STR_STR, Vec::new()) } +#[inline(always)] +fn builtin_alias( + symbol: Symbol, + type_arguments: Vec<(Lowercase, Type)>, + actual: Box, +) -> Type { + Type::Alias { + symbol, + type_arguments, + actual, + lambda_set_variables: vec![], + } +} + #[inline(always)] pub fn num_float(range: Type) -> Type { - Type::Alias( + builtin_alias( Symbol::NUM_FLOAT, vec![("range".into(), range.clone())], Box::new(num_num(num_floatingpoint(range))), @@ -108,7 +122,7 @@ pub fn num_floatingpoint(range: Type) -> Type { Box::new(Type::EmptyTagUnion), ); - Type::Alias( + builtin_alias( Symbol::NUM_FLOATINGPOINT, vec![("range".into(), range)], Box::new(alias_content), @@ -122,12 +136,12 @@ pub fn num_binary64() -> Type { Box::new(Type::EmptyTagUnion), ); - Type::Alias(Symbol::NUM_BINARY64, vec![], Box::new(alias_content)) + builtin_alias(Symbol::NUM_BINARY64, vec![], Box::new(alias_content)) } #[inline(always)] pub fn num_int(range: Type) -> Type { - Type::Alias( + builtin_alias( Symbol::NUM_INT, vec![("range".into(), range.clone())], Box::new(num_num(num_integer(range))), @@ -141,7 +155,7 @@ pub fn num_signed64() -> Type { Box::new(Type::EmptyTagUnion), ); - Type::Alias(Symbol::NUM_SIGNED64, vec![], Box::new(alias_content)) + builtin_alias(Symbol::NUM_SIGNED64, vec![], Box::new(alias_content)) } #[inline(always)] @@ -154,7 +168,7 @@ pub fn num_integer(range: Type) -> Type { Box::new(Type::EmptyTagUnion), ); - Type::Alias( + builtin_alias( Symbol::NUM_INTEGER, vec![("range".into(), range)], Box::new(alias_content), @@ -168,7 +182,7 @@ pub fn num_num(typ: Type) -> Type { Box::new(Type::EmptyTagUnion), ); - Type::Alias( + builtin_alias( Symbol::NUM_NUM, vec![("range".into(), typ)], Box::new(alias_content), diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 05dff22dd7..24bf892102 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1121,7 +1121,7 @@ pub fn constrain_decls(home: ModuleId, decls: &[Declaration]) -> Constraint { } // this assert make the "root" of the constraint wasn't dropped - debug_assert!(format!("{:?}", &constraint).contains("SaveTheEnvironment")); + debug_assert!(constraint.contains_save_the_environment()); constraint } @@ -1446,7 +1446,7 @@ fn instantiate_rigids( let mut rigid_substitution: ImMap = ImMap::default(); for (name, var) in introduced_vars.var_by_name.iter() { - if let Some(existing_rigid) = ftv.get(&name) { + if let Some(existing_rigid) = ftv.get(name) { rigid_substitution.insert(*var, Type::Variable(*existing_rigid)); } else { // It's possible to use this rigid in nested defs diff --git a/compiler/constrain/src/module.rs b/compiler/constrain/src/module.rs index a6462eb570..c8c1a24992 100644 --- a/compiler/constrain/src/module.rs +++ b/compiler/constrain/src/module.rs @@ -57,7 +57,7 @@ pub fn constrain_imported_values( // an imported symbol can be either an alias or a value match import.solved_type { - SolvedType::Alias(symbol, _, _) if symbol == loc_symbol.value => { + SolvedType::Alias(symbol, _, _, _) if symbol == loc_symbol.value => { // do nothing, in the future the alias definitions should not be in the list of imported values } _ => { diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 8a2f5d9250..310a7a251c 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -119,7 +119,7 @@ fn headers_from_annotation_help( } /// This accepts PatternState (rather than returning it) so that the caller can -/// intiialize the Vecs in PatternState using with_capacity +/// initialize the Vecs in PatternState using with_capacity /// based on its knowledge of their lengths. pub fn constrain_pattern( env: &Env, @@ -206,7 +206,7 @@ pub fn constrain_pattern( let pat_type = Type::Variable(*var); let expected = PExpected::NoExpectation(pat_type.clone()); - if !state.headers.contains_key(&symbol) { + if !state.headers.contains_key(symbol) { state .headers .insert(*symbol, Located::at(region, pat_type.clone())); diff --git a/compiler/fmt/Cargo.toml b/compiler/fmt/Cargo.toml index eee0dd515e..5fbeb55b4d 100644 --- a/compiler/fmt/Cargo.toml +++ b/compiler/fmt/Cargo.toml @@ -13,7 +13,6 @@ roc_parse = { path = "../parse" } 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" [dev-dependencies] pretty_assertions = "0.5.1" diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index ebe9036600..54ee550ef6 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -295,7 +295,7 @@ impl<'a> Formattable<'a> for Expr<'a> { items, final_comments, } => { - fmt_list(buf, &items, final_comments, indent); + fmt_list(buf, items, final_comments, indent); } BinOps(lefts, right) => fmt_bin_ops(buf, lefts, right, false, parens, indent), UnaryOp(sub_expr, unary_op) => { @@ -1027,7 +1027,7 @@ fn format_field_multiline<'a, T>( format_field_multiline(buf, sub_field, indent, separator_prefix); } AssignedField::SpaceAfter(sub_field, spaces) => { - // We have somethig like that: + // We have something like that: // ``` // field # comment // , otherfield diff --git a/compiler/gen_dev/Cargo.toml b/compiler/gen_dev/Cargo.toml index 8fb6646e26..40f35682f7 100644 --- a/compiler/gen_dev/Cargo.toml +++ b/compiler/gen_dev/Cargo.toml @@ -21,7 +21,6 @@ roc_mono = { path = "../mono" } 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" target-lexicon = "0.10" libloading = "0.6" object = { version = "0.24", features = ["write"] } diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index af414118f0..a0d3c38187 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -74,7 +74,7 @@ where /// build_proc creates a procedure and outputs it to the wrapped object writer. fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> { self.reset(); - self.load_args(&proc.args)?; + self.load_args(proc.args)?; // let start = std::time::Instant::now(); self.scan_ast(&proc.body); self.create_free_map(); @@ -100,19 +100,6 @@ where self.free_symbols(stmt); Ok(()) } - Stmt::Invoke { - symbol, - layout, - call, - pass, - fail: _, - exception_id: _, - } => { - // for now, treat invoke as a normal call - self.build_expr(symbol, &Expr::Call(call.clone()), layout)?; - self.free_symbols(stmt); - self.build_stmt(pass) - } Stmt::Switch { cond_symbol, cond_layout, @@ -201,6 +188,12 @@ where Symbol::NUM_SUB => { self.build_run_low_level(sym, &LowLevel::NumSub, arguments, layout) } + Symbol::NUM_ROUND => self.build_run_low_level( + sym, + &LowLevel::NumRound, + arguments, + layout, + ), Symbol::BOOL_EQ => { self.build_run_low_level(sym, &LowLevel::Eq, arguments, layout) } @@ -300,7 +293,13 @@ where // Should we panic? x => Err(format!("wrong layout, {:?}, for LowLevel::Eq", x)), }, - + LowLevel::NumRound => self.build_fn_call( + sym, + bitcode::NUM_ROUND.to_string(), + args, + &[Layout::Builtin(Builtin::Float64)], + layout, + ), x => Err(format!("low level, {:?}. is not yet implemented", x)), } } @@ -487,20 +486,6 @@ where self.scan_ast(following); } - Stmt::Invoke { - symbol, - layout: _, - call, - pass, - fail: _, - exception_id: _, - } => { - // for now, treat invoke as a normal call - self.set_last_seen(*symbol, stmt); - self.scan_ast_call(call, stmt); - self.scan_ast(pass); - } - Stmt::Switch { cond_symbol, branches, @@ -516,7 +501,6 @@ where Stmt::Ret(sym) => { self.set_last_seen(*sym, stmt); } - Stmt::Resume(_exception_id) => {} Stmt::Refcounting(modify, following) => { let sym = modify.get_symbol(); diff --git a/compiler/gen_dev/src/object_builder.rs b/compiler/gen_dev/src/object_builder.rs index e3b09d7b52..52ae6b4b08 100644 --- a/compiler/gen_dev/src/object_builder.rs +++ b/compiler/gen_dev/src/object_builder.rs @@ -179,6 +179,12 @@ fn build_object<'a, B: Backend<'a>>( "roc_dealloc".into(), "free".into(), )?; + generate_wrapper( + &mut backend, + &mut output, + "roc_panic".into(), + "roc_builtins.utils.test_panic".into(), + )?; } // Setup layout_ids for procedure calls. diff --git a/compiler/gen_dev/tests/dev_num.rs b/compiler/gen_dev/tests/dev_num.rs index aa758c6587..d1c0ae0d75 100644 --- a/compiler/gen_dev/tests/dev_num.rs +++ b/compiler/gen_dev/tests/dev_num.rs @@ -31,7 +31,7 @@ mod dev_num { 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.1415926535897932, 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); } @@ -287,578 +287,577 @@ mod dev_num { assert_evals_to!("Num.abs 5.8", 5.8, f64); } - /* #[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 f64_round_old() { + fn f64_round() { assert_evals_to!("Num.round 3.6", 4, i64); - } - - - - #[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); + 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] - // #[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)] - // fn int_overflow() { + // fn f64_sqrt() { + // // FIXME this works with normal types, but fails when checking uniqueness types // assert_evals_to!( // indoc!( // r#" - // 9_223_372_036_854_775_807 + 1 + // when Num.sqrt 100 is + // Ok val -> val + // Err _ -> -1 // "# // ), - // 0, + // 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 int_add_checked() { - assert_evals_to!( - indoc!( - r#" - when Num.addChecked 1 2 is - Ok v -> v - _ -> -1 - "# - ), - 3, - i64 - ); + // #[test] + // fn gen_div_by_zero_i64() { + // assert_evals_to!( + // indoc!( + // r#" + // when 1000 // 0 is + // Err DivByZero -> 99 + // _ -> -24 + // "# + // ), + // 99, + // 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 gen_rem_i64() { + // assert_evals_to!( + // indoc!( + // r#" + // when Num.rem 8 3 is + // Ok val -> val + // Err _ -> -1 + // "# + // ), + // 2, + // 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 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 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 gen_is_zero_i64() { + // assert_evals_to!("Num.isZero 0", true, bool); + // assert_evals_to!("Num.isZero 1", false, bool); + // } - #[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] + // 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] - // #[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 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 max_i128() { - assert_evals_to!( - indoc!( - r#" - Num.maxI128 - "# - ), - i128::MAX, - i128 - ); - } + // #[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 num_max_int() { - assert_evals_to!( - indoc!( - r#" - Num.maxInt - "# - ), - i64::MAX, - i64 - ); - } + // #[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 num_min_int() { - assert_evals_to!( - indoc!( - r#" - Num.minInt - "# - ), - i64::MIN, - i64 - ); - } - */ + // #[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_dev/tests/helpers/eval.rs b/compiler/gen_dev/tests/helpers/eval.rs index f3403bb9d4..675f981e72 100644 --- a/compiler/gen_dev/tests/helpers/eval.rs +++ b/compiler/gen_dev/tests/helpers/eval.rs @@ -49,7 +49,7 @@ pub fn helper<'a>( let loaded = roc_load::file::load_and_monomorphize_from_str( arena, filename, - &module_src, + module_src, &stdlib, src_dir, exposed_types, @@ -73,19 +73,22 @@ pub fn helper<'a>( procedures.insert(key, proc); } - /* - println!("=========== Procedures =========="); - println!("{:?}", procedures); - println!("=================================\n"); + // You can comment and uncomment this block out to get more useful information + // while you're working on the dev backend! + { + // println!("=========== Procedures =========="); + // println!("{:?}", procedures); + // println!("=================================\n"); - println!("=========== Interns =========="); - println!("{:?}", interns); - println!("=================================\n"); + // println!("=========== Interns =========="); + // println!("{:?}", interns); + // println!("=================================\n"); + + // println!("=========== Exposed =========="); + // println!("{:?}", exposed_to_host); + // println!("=================================\n"); + } - println!("=========== Exposed =========="); - println!("{:?}", exposed_to_host); - println!("=================================\n"); - */ debug_assert_eq!(exposed_to_host.len(), 1); let main_fn_symbol = loaded.entry_point.symbol; let main_fn_layout = loaded.entry_point.layout; diff --git a/compiler/gen_llvm/Cargo.toml b/compiler/gen_llvm/Cargo.toml index 7fb3060814..3dd95deeaf 100644 --- a/compiler/gen_llvm/Cargo.toml +++ b/compiler/gen_llvm/Cargo.toml @@ -20,7 +20,6 @@ morphic_lib = { path = "../../vendor/morphic_lib" } 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" inkwell = { path = "../../vendor/inkwell" } target-lexicon = "0.10" diff --git a/compiler/gen_llvm/src/llvm/bitcode.rs b/compiler/gen_llvm/src/llvm/bitcode.rs index b92df1f79b..f873af0518 100644 --- a/compiler/gen_llvm/src/llvm/bitcode.rs +++ b/compiler/gen_llvm/src/llvm/bitcode.rs @@ -78,7 +78,7 @@ pub fn build_has_tag_id<'a, 'ctx, 'env>( match env.module.get_function(fn_name) { Some(function_value) => function_value, - None => build_has_tag_id_help(env, union_layout, &fn_name), + None => build_has_tag_id_help(env, union_layout, fn_name), } } @@ -97,9 +97,9 @@ fn build_has_tag_id_help<'a, 'ctx, 'env>( let function_value = crate::llvm::refcounting::build_header_help( env, - &fn_name, + fn_name, output_type.into(), - &argument_types, + argument_types, ); // called from zig, must use C calling convention @@ -121,7 +121,7 @@ fn build_has_tag_id_help<'a, 'ctx, 'env>( bumpalo::collections::Vec::from_iter_in(it.take(argument_types.len()), env.arena); for (argument, name) in arguments.iter().zip(ARGUMENT_SYMBOLS.iter()) { - argument.set_name(name.ident_string(&env.interns)); + argument.set_name(name.as_str(&env.interns)); } match arguments.as_slice() { @@ -204,7 +204,7 @@ pub fn build_transform_caller<'a, 'ctx, 'env>( function, closure_data_layout, argument_layouts, - &fn_name, + fn_name, ), } } @@ -225,7 +225,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>( let function_value = crate::llvm::refcounting::build_header_help( env, - &fn_name, + fn_name, env.context.void_type().into(), &(bumpalo::vec![ in env.arena; BasicTypeEnum::PointerType(arg_type); argument_layouts.len() + 2 ]), ); @@ -245,13 +245,13 @@ fn build_transform_caller_help<'a, 'ctx, 'env>( let mut it = function_value.get_param_iter(); let closure_ptr = it.next().unwrap().into_pointer_value(); - closure_ptr.set_name(Symbol::ARG_1.ident_string(&env.interns)); + closure_ptr.set_name(Symbol::ARG_1.as_str(&env.interns)); let arguments = bumpalo::collections::Vec::from_iter_in(it.take(argument_layouts.len()), env.arena); for (argument, name) in arguments.iter().zip(ARGUMENT_SYMBOLS[1..].iter()) { - argument.set_name(name.ident_string(&env.interns)); + argument.set_name(name.as_str(&env.interns)); } let mut arguments_cast = @@ -271,33 +271,11 @@ fn build_transform_caller_help<'a, 'ctx, 'env>( } match closure_data_layout { - Layout::Closure(_, lambda_set, _) => { - if let Layout::Struct(&[]) = lambda_set.runtime_representation() { - // do nothing - } else { - let closure_type = - basic_type_from_layout(env, &lambda_set.runtime_representation()) - .ptr_type(AddressSpace::Generic); - - let closure_cast = env - .builder - .build_bitcast(closure_ptr, closure_type, "load_opaque") - .into_pointer_value(); - - let closure_data = env.builder.build_load(closure_cast, "load_opaque"); - - arguments_cast.push(closure_data); - } + Layout::Struct(&[]) => { + // nothing to add } - Layout::Struct([Layout::Closure(_, lambda_set, _)]) => { - // a case required for Set.walk; may be able to remove when we can define builtins in - // terms of other builtins in the right way (using their function symbols instead of - // hacking with lowlevel ops). - let closure_type = basic_type_from_layout( - env, - &Layout::Struct(&[lambda_set.runtime_representation()]), - ) - .ptr_type(AddressSpace::Generic); + other => { + let closure_type = basic_type_from_layout(env, &other).ptr_type(AddressSpace::Generic); let closure_cast = env .builder @@ -308,13 +286,6 @@ fn build_transform_caller_help<'a, 'ctx, 'env>( arguments_cast.push(closure_data); } - Layout::Struct([]) => { - // do nothing, should try to remove this case later - } - Layout::Struct(_) => { - // do nothing, should try to remove this case later - } - other => unreachable!("layout is not valid for a closure: {:?}", other), } let call = { @@ -394,7 +365,7 @@ fn build_rc_wrapper<'a, 'ctx, 'env>( let symbol = Symbol::GENERIC_RC_REF; let fn_name = layout_ids - .get(symbol, &layout) + .get(symbol, layout) .to_symbol_string(symbol, &env.interns); let fn_name = match rc_operation { @@ -439,7 +410,7 @@ fn build_rc_wrapper<'a, 'ctx, 'env>( let mut it = function_value.get_param_iter(); let value_ptr = it.next().unwrap().into_pointer_value(); - value_ptr.set_name(Symbol::ARG_1.ident_string(&env.interns)); + value_ptr.set_name(Symbol::ARG_1.as_str(&env.interns)); let value_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic); @@ -457,7 +428,7 @@ fn build_rc_wrapper<'a, 'ctx, 'env>( } Mode::IncN => { let n = it.next().unwrap().into_int_value(); - n.set_name(Symbol::ARG_2.ident_string(&env.interns)); + n.set_name(Symbol::ARG_2.as_str(&env.interns)); increment_n_refcount_layout(env, function_value, layout_ids, n, value, layout); } @@ -489,7 +460,7 @@ pub fn build_eq_wrapper<'a, 'ctx, 'env>( let symbol = Symbol::GENERIC_EQ_REF; let fn_name = layout_ids - .get(symbol, &layout) + .get(symbol, layout) .to_symbol_string(symbol, &env.interns); let function_value = match env.module.get_function(fn_name.as_str()) { @@ -521,8 +492,8 @@ pub fn build_eq_wrapper<'a, 'ctx, 'env>( let value_ptr1 = it.next().unwrap().into_pointer_value(); let value_ptr2 = it.next().unwrap().into_pointer_value(); - value_ptr1.set_name(Symbol::ARG_1.ident_string(&env.interns)); - value_ptr2.set_name(Symbol::ARG_2.ident_string(&env.interns)); + value_ptr1.set_name(Symbol::ARG_1.as_str(&env.interns)); + value_ptr2.set_name(Symbol::ARG_2.as_str(&env.interns)); let value_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic); @@ -576,7 +547,7 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>( let function_value = crate::llvm::refcounting::build_header_help( env, - &fn_name, + fn_name, env.context.i8_type().into(), &[arg_type.into(), arg_type.into(), arg_type.into()], ); @@ -602,9 +573,9 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>( let value_ptr1 = it.next().unwrap().into_pointer_value(); let value_ptr2 = it.next().unwrap().into_pointer_value(); - closure_ptr.set_name(Symbol::ARG_1.ident_string(&env.interns)); - value_ptr1.set_name(Symbol::ARG_2.ident_string(&env.interns)); - value_ptr2.set_name(Symbol::ARG_3.ident_string(&env.interns)); + closure_ptr.set_name(Symbol::ARG_1.as_str(&env.interns)); + value_ptr1.set_name(Symbol::ARG_2.as_str(&env.interns)); + value_ptr2.set_name(Symbol::ARG_3.as_str(&env.interns)); let value_type = basic_type_from_layout(env, layout); let value_ptr_type = value_type.ptr_type(AddressSpace::Generic); @@ -625,26 +596,23 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>( let default = [value1, value2]; let arguments_cast = match closure_data_layout { - Layout::Closure(_, lambda_set, _) => { - if let Layout::Struct(&[]) = lambda_set.runtime_representation() { - &default - } else { - let closure_type = - basic_type_from_layout(env, &lambda_set.runtime_representation()) - .ptr_type(AddressSpace::Generic); - - let closure_cast = env - .builder - .build_bitcast(closure_ptr, closure_type, "load_opaque") - .into_pointer_value(); - - let closure_data = env.builder.build_load(closure_cast, "load_opaque"); - - env.arena.alloc([value1, value2, closure_data]) as &[_] - } + Layout::Struct(&[]) => { + // nothing to add + &default + } + other => { + let closure_type = + basic_type_from_layout(env, &other).ptr_type(AddressSpace::Generic); + + let closure_cast = env + .builder + .build_bitcast(closure_ptr, closure_type, "load_opaque") + .into_pointer_value(); + + let closure_data = env.builder.build_load(closure_cast, "load_opaque"); + + env.arena.alloc([value1, value2, closure_data]) as &[_] } - Layout::Struct([]) => &default, - other => unreachable!("layout is not valid for a closure: {:?}", other), }; let call = env.builder.build_call( diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 6e87423b3f..2142b2e92e 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -1,6 +1,6 @@ use std::path::Path; -use crate::llvm::bitcode::call_bitcode_fn; +use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn}; use crate::llvm::build_dict::{ dict_contains, dict_difference, dict_empty, dict_get, dict_insert, dict_intersection, dict_keys, dict_len, dict_remove, dict_union, dict_values, dict_walk, set_from_list, @@ -14,16 +14,15 @@ use crate::llvm::build_list::{ }; 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_join_with, str_number_of_bytes, str_split, str_starts_with, - str_starts_with_code_point, str_to_bytes, + str_from_utf8, str_from_utf8_range, str_join_with, str_number_of_bytes, str_split, + str_starts_with, str_starts_with_code_point, str_to_utf8, }; use crate::llvm::compare::{generic_eq, generic_neq}; use crate::llvm::convert::{ - basic_type_from_builtin, basic_type_from_layout, block_of_memory, block_of_memory_slices, - ptr_int, + basic_type_from_builtin, basic_type_from_layout, block_of_memory_slices, ptr_int, }; use crate::llvm::refcounting::{ - decrement_refcount_layout, increment_refcount_layout, PointerToRefcount, + build_reset, decrement_refcount_layout, increment_refcount_layout, PointerToRefcount, }; use bumpalo::collections::Vec; use bumpalo::Bump; @@ -51,9 +50,7 @@ use roc_builtins::bitcode; use roc_collections::all::{ImMap, MutMap, MutSet}; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, ModuleId, Symbol}; -use roc_mono::ir::{ - BranchInfo, CallType, EntryPoint, ExceptionId, JoinPointId, ModifyRc, OptLevel, ProcLayout, -}; +use roc_mono::ir::{BranchInfo, CallType, EntryPoint, JoinPointId, ModifyRc, OptLevel, ProcLayout}; use roc_mono::layout::{Builtin, LambdaSet, Layout, LayoutIds, UnionLayout}; /// This is for Inkwell's FunctionValue::verify - we want to know the verification @@ -69,7 +66,7 @@ macro_rules! debug_info_init { ($env:expr, $function_value:expr) => {{ use inkwell::debug_info::AsDIScope; - let func_scope = $function_value.get_subprogram().unwrap(); + let func_scope = $function_value.get_subprogram().expect("subprogram"); let lexical_block = $env.dibuilder.create_lexical_block( /* scope */ func_scope.as_debug_info_scope(), /* file */ $env.compile_unit.get_file(), @@ -158,10 +155,26 @@ pub struct Env<'a, 'ctx, 'env> { pub module: &'ctx Module<'ctx>, pub interns: Interns, pub ptr_bytes: u32, - pub leak: bool, + pub is_gen_test: bool, pub exposed_to_host: MutSet, } +#[repr(u32)] +pub enum PanicTagId { + NullTerminatedString = 0, +} + +impl std::convert::TryFrom for PanicTagId { + type Error = (); + + fn try_from(value: u32) -> Result { + match value { + 0 => Ok(PanicTagId::NullTerminatedString), + _ => Err(()), + } + } +} + impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> { pub fn ptr_int(&self) -> IntType<'ctx> { ptr_int(self.context, self.ptr_bytes) @@ -287,6 +300,20 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> { ) } + pub fn call_panic(&self, message: PointerValue<'ctx>, tag_id: PanicTagId) { + let function = self.module.get_function("roc_panic").unwrap(); + let tag_id = self + .context + .i32_type() + .const_int(tag_id as u32 as u64, false); + + let call = self + .builder + .build_call(function, &[message.into(), tag_id.into()], "roc_panic"); + + call.set_call_convention(C_CALL_CONV); + } + pub fn new_debug_info(module: &Module<'ctx>) -> (DebugInfoBuilder<'ctx>, DICompileUnit<'ctx>) { module.create_debug_info_builder( true, @@ -348,7 +375,7 @@ pub fn module_from_builtins<'ctx>(ctx: &'ctx Context, module_name: &str) -> Modu // we compile the builtins into LLVM bitcode let bitcode_bytes: &[u8] = include_bytes!("../../../builtins/bitcode/builtins.bc"); - let memory_buffer = MemoryBuffer::create_from_memory_range(&bitcode_bytes, module_name); + let memory_buffer = MemoryBuffer::create_from_memory_range(bitcode_bytes, module_name); let module = Module::parse_bitcode_from_buffer(&memory_buffer, ctx) .unwrap_or_else(|err| panic!("Unable to import builtins bitcode. LLVM error: {:?}", err)); @@ -363,13 +390,46 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { // List of all supported LLVM intrinsics: // // https://releases.llvm.org/10.0.0/docs/LangRef.html#standard-c-library-intrinsics - let i1_type = ctx.bool_type(); let f64_type = ctx.f64_type(); - let i128_type = ctx.i128_type(); - let i64_type = ctx.i64_type(); - let i32_type = ctx.i32_type(); - let i16_type = ctx.i16_type(); + let i1_type = ctx.bool_type(); let i8_type = ctx.i8_type(); + let i8_ptr_type = i8_type.ptr_type(AddressSpace::Generic); + let i16_type = ctx.i16_type(); + let i32_type = ctx.i32_type(); + let i64_type = ctx.i64_type(); + let void_type = ctx.void_type(); + + if let Some(func) = module.get_function("__muloti4") { + func.set_linkage(Linkage::WeakAny); + } + + add_intrinsic( + module, + LLVM_SETJMP, + i32_type.fn_type(&[i8_ptr_type.into()], false), + ); + + if true { + add_intrinsic( + module, + LLVM_LONGJMP, + void_type.fn_type(&[i8_ptr_type.into()], false), + ); + } else { + add_intrinsic( + module, + LLVM_LONGJMP, + void_type.fn_type(&[i8_ptr_type.into(), i32_type.into()], false), + ); + } + + add_intrinsic( + module, + LLVM_FRAME_ADDRESS, + i8_ptr_type.fn_type(&[i32_type.into()], false), + ); + + add_intrinsic(module, LLVM_STACK_SAVE, i8_ptr_type.fn_type(&[], false)); add_intrinsic( module, @@ -419,8 +479,6 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { f64_type.fn_type(&[f64_type.into()], false), ); - // add with overflow - add_intrinsic(module, LLVM_SADD_WITH_OVERFLOW_I8, { let fields = [i8_type.into(), i1_type.into()]; ctx.struct_type(&fields, false) @@ -445,14 +503,6 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { .fn_type(&[i64_type.into(), i64_type.into()], false) }); - add_intrinsic(module, LLVM_SADD_WITH_OVERFLOW_I128, { - let fields = [i128_type.into(), i1_type.into()]; - ctx.struct_type(&fields, false) - .fn_type(&[i128_type.into(), i128_type.into()], false) - }); - - // sub with overflow - add_intrinsic(module, LLVM_SSUB_WITH_OVERFLOW_I8, { let fields = [i8_type.into(), i1_type.into()]; ctx.struct_type(&fields, false) @@ -476,12 +526,6 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { ctx.struct_type(&fields, false) .fn_type(&[i64_type.into(), i64_type.into()], false) }); - - add_intrinsic(module, LLVM_SSUB_WITH_OVERFLOW_I128, { - let fields = [i128_type.into(), i1_type.into()]; - ctx.struct_type(&fields, false) - .fn_type(&[i128_type.into(), i128_type.into()], false) - }); } static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64"; @@ -496,6 +540,13 @@ static LLVM_POW_F64: &str = "llvm.pow.f64"; static LLVM_CEILING_F64: &str = "llvm.ceil.f64"; static LLVM_FLOOR_F64: &str = "llvm.floor.f64"; +// static LLVM_FRAME_ADDRESS: &str = "llvm.frameaddress"; +static LLVM_FRAME_ADDRESS: &str = "llvm.frameaddress.p0i8"; +static LLVM_STACK_SAVE: &str = "llvm.stacksave"; + +static LLVM_SETJMP: &str = "llvm.eh.sjlj.setjmp"; +pub static LLVM_LONGJMP: &str = "llvm.eh.sjlj.longjmp"; + pub static LLVM_SADD_WITH_OVERFLOW_I8: &str = "llvm.sadd.with.overflow.i8"; pub static LLVM_SADD_WITH_OVERFLOW_I16: &str = "llvm.sadd.with.overflow.i16"; pub static LLVM_SADD_WITH_OVERFLOW_I32: &str = "llvm.sadd.with.overflow.i32"; @@ -610,12 +661,7 @@ fn promote_to_main_function<'a, 'ctx, 'env>( let main_fn_name = "$Test.main"; // Add main to the module. - let main_fn = expose_function_to_host_help( - env, - &inlinable_string::InlinableString::from(main_fn_name), - roc_main_fn, - main_fn_name, - ); + let main_fn = expose_function_to_host_help(env, main_fn_name, roc_main_fn, main_fn_name); (main_fn_name, main_fn) } @@ -641,10 +687,15 @@ pub fn float_with_precision<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, value: f64, precision: &Builtin, -) -> FloatValue<'ctx> { +) -> BasicValueEnum<'ctx> { match precision { - Builtin::Float64 => env.context.f64_type().const_float(value), - Builtin::Float32 => env.context.f32_type().const_float(value), + Builtin::Decimal => call_bitcode_fn( + env, + &[env.context.f64_type().const_float(value).into()], + bitcode::DEC_FROM_F64, + ), + Builtin::Float64 => env.context.f64_type().const_float(value).into(), + Builtin::Float32 => env.context.f32_type().const_float(value).into(), _ => panic!("Invalid layout for float literal = {:?}", precision), } } @@ -663,7 +714,7 @@ pub fn build_exp_literal<'a, 'ctx, 'env>( }, Float(float) => match layout { - Layout::Builtin(builtin) => float_with_precision(env, *float, builtin).into(), + Layout::Builtin(builtin) => float_with_precision(env, *float, builtin), _ => panic!("Invalid layout for float literal = {:?}", layout), }, @@ -879,22 +930,12 @@ pub fn build_exp_call<'a, 'ctx, 'env>( } => { // we always initially invoke foreign symbols, but if there is nothing to clean up, // we emit a normal call - build_foreign_symbol( - env, - layout_ids, - func_spec_solutions, - scope, - parent, - foreign_symbol, - arguments, - ret_layout, - ForeignCallOrInvoke::Call, - ) + build_foreign_symbol(env, scope, foreign_symbol, arguments, ret_layout) } } } -pub const TAG_ID_INDEX: u32 = 1; +const TAG_ID_INDEX: u32 = 1; pub const TAG_DATA_INDEX: u32 = 0; pub fn struct_from_fields<'a, 'ctx, 'env, I>( @@ -920,6 +961,34 @@ where struct_value.into_struct_value() } +fn struct_pointer_from_fields<'a, 'ctx, 'env, I>( + env: &Env<'a, 'ctx, 'env>, + struct_type: StructType<'ctx>, + input_pointer: PointerValue<'ctx>, + values: I, +) where + I: Iterator)>, +{ + let struct_ptr = env + .builder + .build_bitcast( + input_pointer, + struct_type.ptr_type(AddressSpace::Generic), + "struct_ptr", + ) + .into_pointer_value(); + + // Insert field exprs into struct_val + for (index, field_val) in values { + let field_ptr = env + .builder + .build_struct_gep(struct_ptr, index as u32, "field_struct_gep") + .unwrap(); + + env.builder.build_store(field_ptr, field_val); + } +} + pub fn build_exp_expr<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, @@ -957,7 +1026,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( // The layout of the struct expects them to be dropped! let (field_expr, field_layout) = load_symbol_and_layout(scope, symbol); if !field_layout.is_dropped_because_empty() { - field_types.push(basic_type_from_layout(env, &field_layout)); + field_types.push(basic_type_from_layout(env, field_layout)); field_vals.push(field_expr); } @@ -970,16 +1039,87 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( struct_from_fields(env, struct_type, field_vals.into_iter().enumerate()).into() } + Reuse { + arguments, + tag_layout: union_layout, + tag_id, + symbol, + .. + } => { + let reset = load_symbol(scope, symbol).into_pointer_value(); + build_tag( + env, + scope, + union_layout, + *tag_id, + arguments, + Some(reset), + parent, + ) + } + Tag { arguments, tag_layout: union_layout, - union_size, tag_id, .. - } => build_tag(env, scope, union_layout, *union_size, *tag_id, arguments), + } => build_tag(env, scope, union_layout, *tag_id, arguments, None, parent), - Reset(_) => todo!(), - Reuse { .. } => todo!(), + Reset(symbol) => { + let (tag_ptr, layout) = load_symbol_and_layout(scope, symbol); + let tag_ptr = tag_ptr.into_pointer_value(); + + // reset is only generated for union values + let union_layout = match layout { + Layout::Union(ul) => ul, + _ => unreachable!(), + }; + + let ctx = env.context; + let then_block = ctx.append_basic_block(parent, "then_reset"); + let else_block = ctx.append_basic_block(parent, "else_decref"); + let cont_block = ctx.append_basic_block(parent, "cont"); + + let refcount_ptr = + PointerToRefcount::from_ptr_to_data(env, tag_pointer_clear_tag_id(env, tag_ptr)); + let is_unique = refcount_ptr.is_1(env); + + env.builder + .build_conditional_branch(is_unique, then_block, else_block); + + { + // reset, when used on a unique reference, eagerly decrements the components of the + // referenced value, and returns the location of the now-invalid cell + env.builder.position_at_end(then_block); + + let reset_function = build_reset(env, layout_ids, *union_layout); + let call = env + .builder + .build_call(reset_function, &[tag_ptr.into()], "call_reset"); + + call.set_call_convention(FAST_CALL_CONV); + + let _ = call.try_as_basic_value(); + + env.builder.build_unconditional_branch(cont_block); + } + { + // If reset is used on a shared, non-reusable reference, it behaves + // like dec and returns NULL, which instructs reuse to behave like ctor + env.builder.position_at_end(else_block); + refcount_ptr.decrement(env, layout); + env.builder.build_unconditional_branch(cont_block); + } + { + env.builder.position_at_end(cont_block); + let phi = env.builder.build_phi(tag_ptr.get_type(), "branch"); + + let null_ptr = tag_ptr.get_type().const_null(); + phi.add_incoming(&[(&tag_ptr, then_block), (&null_ptr, else_block)]); + + phi.as_basic_value() + } + } StructAtIndex { index, structure, .. @@ -997,14 +1137,6 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( ) .unwrap() } - (StructValue(argument), Layout::Closure(_, _, _)) => env - .builder - .build_extract_value( - argument, - *index as u32, - env.arena.alloc(format!("closure_field_access_{}_", index)), - ) - .unwrap(), ( PointerValue(argument), Layout::Union(UnionLayout::NonNullableUnwrapped(fields)), @@ -1085,26 +1217,29 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( let tag_id_type = basic_type_from_layout(env, &union_layout.tag_id_layout()).into_int_type(); + let ptr = tag_pointer_clear_tag_id(env, argument.into_pointer_value()); + lookup_at_index_ptr2( env, + union_layout, tag_id_type, field_layouts, *index as usize, - argument.into_pointer_value(), + ptr, ) } UnionLayout::NonNullableUnwrapped(field_layouts) => { - let struct_layout = Layout::Struct(&field_layouts); + let struct_layout = Layout::Struct(field_layouts); let struct_type = basic_type_from_layout(env, &struct_layout); lookup_at_index_ptr( env, + union_layout, field_layouts, *index as usize, argument.into_pointer_value(), struct_type.into_struct_type(), - &struct_layout, ) } UnionLayout::NullableWrapped { @@ -1125,12 +1260,14 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( let tag_id_type = basic_type_from_layout(env, &union_layout.tag_id_layout()).into_int_type(); + let ptr = tag_pointer_clear_tag_id(env, argument.into_pointer_value()); lookup_at_index_ptr2( env, + union_layout, tag_id_type, field_layouts, *index as usize, - argument.into_pointer_value(), + ptr, ) } UnionLayout::NullableUnwrapped { @@ -1147,12 +1284,12 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( lookup_at_index_ptr( env, + union_layout, field_layouts, // the tag id is not stored *index as usize, argument.into_pointer_value(), struct_type.into_struct_type(), - &struct_layout, ) } } @@ -1165,27 +1302,114 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( // cast the argument bytes into the desired shape for this tag let (argument, _structure_layout) = load_symbol_and_layout(scope, structure); - get_tag_id(env, parent, &union_layout, argument).into() + get_tag_id(env, parent, union_layout, argument).into() } } } +#[allow(clippy::too_many_arguments)] +fn build_wrapped_tag<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + scope: &Scope<'a, 'ctx>, + union_layout: &UnionLayout<'a>, + tag_id: u8, + arguments: &[Symbol], + tag_field_layouts: &[Layout<'a>], + tags: &[&[Layout<'a>]], + reuse_allocation: Option>, + parent: FunctionValue<'ctx>, +) -> BasicValueEnum<'ctx> { + let ctx = env.context; + let builder = env.builder; + + let tag_id_layout = union_layout.tag_id_layout(); + + // Determine types + let num_fields = arguments.len() + 1; + let mut field_types = Vec::with_capacity_in(num_fields, env.arena); + let mut field_vals = Vec::with_capacity_in(num_fields, env.arena); + + for (field_symbol, tag_field_layout) in arguments.iter().zip(tag_field_layouts.iter()) { + let (val, _val_layout) = load_symbol_and_layout(scope, field_symbol); + + let field_type = basic_type_from_layout(env, tag_field_layout); + + field_types.push(field_type); + + if let Layout::RecursivePointer = tag_field_layout { + debug_assert!(val.is_pointer_value()); + + // we store recursive pointers as `i64*` + let ptr = env.builder.build_bitcast( + val, + ctx.i64_type().ptr_type(AddressSpace::Generic), + "cast_recursive_pointer", + ); + + field_vals.push(ptr); + } else { + // this check fails for recursive tag unions, but can be helpful while debugging + // debug_assert_eq!(tag_field_layout, val_layout); + + field_vals.push(val); + } + } + + // Create the struct_type + let raw_data_ptr = allocate_tag(env, parent, reuse_allocation, union_layout, tags); + let struct_type = env.context.struct_type(&field_types, false); + + if union_layout.stores_tag_id_as_data(env.ptr_bytes) { + let tag_id_ptr = builder + .build_struct_gep(raw_data_ptr, TAG_ID_INDEX, "tag_id_index") + .unwrap(); + + let tag_id_type = basic_type_from_layout(env, &tag_id_layout).into_int_type(); + + env.builder + .build_store(tag_id_ptr, tag_id_type.const_int(tag_id as u64, false)); + + let opaque_struct_ptr = builder + .build_struct_gep(raw_data_ptr, TAG_DATA_INDEX, "tag_data_index") + .unwrap(); + + struct_pointer_from_fields( + env, + struct_type, + opaque_struct_ptr, + field_vals.into_iter().enumerate(), + ); + + raw_data_ptr.into() + } else { + struct_pointer_from_fields( + env, + struct_type, + raw_data_ptr, + field_vals.into_iter().enumerate(), + ); + + tag_pointer_set_tag_id(env, tag_id, raw_data_ptr).into() + } +} + pub fn build_tag<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, scope: &Scope<'a, 'ctx>, union_layout: &UnionLayout<'a>, - union_size: u8, tag_id: u8, arguments: &[Symbol], + reuse_allocation: Option>, + parent: FunctionValue<'ctx>, ) -> BasicValueEnum<'ctx> { let tag_id_layout = union_layout.tag_id_layout(); + let union_size = union_layout.number_of_tags(); match union_layout { UnionLayout::NonRecursive(tags) => { debug_assert!(union_size > 1); let ctx = env.context; - let builder = env.builder; // Determine types let num_fields = arguments.len() + 1; @@ -1250,7 +1474,7 @@ pub fn build_tag<'a, 'ctx, 'env>( let internal_type = block_of_memory_slices(env.context, tags, env.ptr_bytes); - let data = cast_tag_to_block_of_memory(builder, struct_val, internal_type); + let data = cast_tag_to_block_of_memory(env, struct_val, internal_type); let tag_id_type = basic_type_from_layout(env, &tag_id_layout).into_int_type(); let wrapper_type = env .context @@ -1268,79 +1492,51 @@ pub fn build_tag<'a, 'ctx, 'env>( UnionLayout::Recursive(tags) => { debug_assert!(union_size > 1); - let ctx = env.context; - let builder = env.builder; - - // Determine types - let num_fields = arguments.len() + 1; - let mut field_types = Vec::with_capacity_in(num_fields, env.arena); - let mut field_vals = Vec::with_capacity_in(num_fields, env.arena); - let tag_field_layouts = &tags[tag_id as usize]; - for (field_symbol, tag_field_layout) in arguments.iter().zip(tag_field_layouts.iter()) { - let (val, _val_layout) = load_symbol_and_layout(scope, field_symbol); + build_wrapped_tag( + env, + scope, + union_layout, + tag_id, + arguments, + tag_field_layouts, + tags, + reuse_allocation, + parent, + ) + } + UnionLayout::NullableWrapped { + nullable_id, + other_tags: tags, + } => { + let tag_field_layouts = { + use std::cmp::Ordering::*; + match tag_id.cmp(&(*nullable_id as u8)) { + Equal => { + let layout = Layout::Union(*union_layout); - let field_type = basic_type_from_layout(env, tag_field_layout); - - field_types.push(field_type); - - if let Layout::RecursivePointer = tag_field_layout { - debug_assert!(val.is_pointer_value()); - - // we store recursive pointers as `i64*` - let ptr = env.builder.build_bitcast( - val, - ctx.i64_type().ptr_type(AddressSpace::Generic), - "cast_recursive_pointer", - ); - - field_vals.push(ptr); - } else { - // this check fails for recursive tag unions, but can be helpful while debugging - // debug_assert_eq!(tag_field_layout, val_layout); - - field_vals.push(val); + return basic_type_from_layout(env, &layout) + .into_pointer_type() + .const_null() + .into(); + } + Less => &tags[tag_id as usize], + Greater => &tags[tag_id as usize - 1], } - } + }; - // Create the struct_type - let raw_data_ptr = - reserve_with_refcount_union_as_block_of_memory(env, *union_layout, tags); - - let tag_id_ptr = builder - .build_struct_gep(raw_data_ptr, TAG_ID_INDEX, "tag_id_index") - .unwrap(); - - let tag_id_type = basic_type_from_layout(env, &tag_id_layout).into_int_type(); - - env.builder - .build_store(tag_id_ptr, tag_id_type.const_int(tag_id as u64, false)); - - let opaque_struct_ptr = builder - .build_struct_gep(raw_data_ptr, TAG_DATA_INDEX, "tag_data_index") - .unwrap(); - - let struct_type = env.context.struct_type(&field_types, false); - let struct_ptr = env - .builder - .build_bitcast( - opaque_struct_ptr, - struct_type.ptr_type(AddressSpace::Generic), - "struct_ptr", - ) - .into_pointer_value(); - - // Insert field exprs into struct_val - for (index, field_val) in field_vals.into_iter().enumerate() { - let field_ptr = builder - .build_struct_gep(struct_ptr, index as u32, "field_struct_gep") - .unwrap(); - - builder.build_store(field_ptr, field_val); - } - - raw_data_ptr.into() + build_wrapped_tag( + env, + scope, + union_layout, + tag_id, + arguments, + tag_field_layouts, + tags, + reuse_allocation, + parent, + ) } UnionLayout::NonNullableUnwrapped(fields) => { debug_assert_eq!(union_size, 1); @@ -1348,7 +1544,6 @@ pub fn build_tag<'a, 'ctx, 'env>( debug_assert_eq!(arguments.len(), fields.len()); let ctx = env.context; - let builder = env.builder; // Determine types let num_fields = arguments.len() + 1; @@ -1385,126 +1580,16 @@ pub fn build_tag<'a, 'ctx, 'env>( reserve_with_refcount_union_as_block_of_memory(env, *union_layout, &[fields]); let struct_type = ctx.struct_type(field_types.into_bump_slice(), false); - let struct_ptr = env - .builder - .build_bitcast( - data_ptr, - struct_type.ptr_type(AddressSpace::Generic), - "block_of_memory_to_tag", - ) - .into_pointer_value(); - // Insert field exprs into struct_val - for (index, field_val) in field_vals.into_iter().enumerate() { - let field_ptr = builder - .build_struct_gep(struct_ptr, index as u32, "struct_gep") - .unwrap(); - - builder.build_store(field_ptr, field_val); - } + struct_pointer_from_fields( + env, + struct_type, + data_ptr, + field_vals.into_iter().enumerate(), + ); data_ptr.into() } - UnionLayout::NullableWrapped { - nullable_id, - other_tags: tags, - } => { - if tag_id == *nullable_id as u8 { - let layout = Layout::Union(*union_layout); - - return basic_type_from_layout(env, &layout) - .into_pointer_type() - .const_null() - .into(); - } - - debug_assert!(union_size > 1); - - let ctx = env.context; - let builder = env.builder; - - // Determine types - let num_fields = arguments.len() + 1; - let mut field_types = Vec::with_capacity_in(num_fields, env.arena); - let mut field_vals = Vec::with_capacity_in(num_fields, env.arena); - - let tag_field_layouts = { - use std::cmp::Ordering::*; - match tag_id.cmp(&(*nullable_id as u8)) { - Equal => unreachable!("early return above"), - Less => &tags[tag_id as usize], - Greater => &tags[tag_id as usize - 1], - } - }; - - for (field_symbol, tag_field_layout) in arguments.iter().zip(tag_field_layouts.iter()) { - let val = load_symbol(scope, field_symbol); - - // Zero-sized fields have no runtime representation. - // The layout of the struct expects them to be dropped! - if !tag_field_layout.is_dropped_because_empty() { - let field_type = basic_type_from_layout(env, tag_field_layout); - - field_types.push(field_type); - - if let Layout::RecursivePointer = tag_field_layout { - debug_assert!(val.is_pointer_value()); - - // we store recursive pointers as `i64*` - let ptr = env.builder.build_bitcast( - val, - ctx.i64_type().ptr_type(AddressSpace::Generic), - "cast_recursive_pointer", - ); - - field_vals.push(ptr); - } else { - // this check fails for recursive tag unions, but can be helpful while debugging - // debug_assert_eq!(tag_field_layout, val_layout); - - field_vals.push(val); - } - } - } - - // Create the struct_type - let raw_data_ptr = - reserve_with_refcount_union_as_block_of_memory(env, *union_layout, tags); - - let tag_id_ptr = builder - .build_struct_gep(raw_data_ptr, TAG_ID_INDEX, "tag_id_index") - .unwrap(); - - let tag_id_type = basic_type_from_layout(env, &tag_id_layout).into_int_type(); - - env.builder - .build_store(tag_id_ptr, tag_id_type.const_int(tag_id as u64, false)); - - let opaque_struct_ptr = builder - .build_struct_gep(raw_data_ptr, TAG_DATA_INDEX, "tag_data_index") - .unwrap(); - - let struct_type = env.context.struct_type(&field_types, false); - let struct_ptr = env - .builder - .build_bitcast( - opaque_struct_ptr, - struct_type.ptr_type(AddressSpace::Generic), - "struct_ptr", - ) - .into_pointer_value(); - - // Insert field exprs into struct_val - for (index, field_val) in field_vals.into_iter().enumerate() { - let field_ptr = builder - .build_struct_gep(struct_ptr, index as u32, "field_struct_gep") - .unwrap(); - - builder.build_store(field_ptr, field_val); - } - - raw_data_ptr.into() - } UnionLayout::NullableUnwrapped { nullable_id, other_fields, @@ -1525,7 +1610,6 @@ pub fn build_tag<'a, 'ctx, 'env>( debug_assert!(union_size == 2); let ctx = env.context; - let builder = env.builder; // Determine types let num_fields = arguments.len() + 1; @@ -1566,32 +1650,128 @@ pub fn build_tag<'a, 'ctx, 'env>( // Create the struct_type let data_ptr = - reserve_with_refcount_union_as_block_of_memory(env, *union_layout, &[other_fields]); + allocate_tag(env, parent, reuse_allocation, union_layout, &[other_fields]); let struct_type = ctx.struct_type(field_types.into_bump_slice(), false); - let struct_ptr = env - .builder - .build_bitcast( - data_ptr, - struct_type.ptr_type(AddressSpace::Generic), - "block_of_memory_to_tag", - ) - .into_pointer_value(); - // Insert field exprs into struct_val - for (index, field_val) in field_vals.into_iter().enumerate() { - let field_ptr = builder - .build_struct_gep(struct_ptr, index as u32, "struct_gep") - .unwrap(); - - builder.build_store(field_ptr, field_val); - } + struct_pointer_from_fields( + env, + struct_type, + data_ptr, + field_vals.into_iter().enumerate(), + ); data_ptr.into() } } } +fn tag_pointer_set_tag_id<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + tag_id: u8, + pointer: PointerValue<'ctx>, +) -> PointerValue<'ctx> { + // we only have 3 bits, so can encode only 0..7 + debug_assert!(tag_id < 8); + + let ptr_int = env.ptr_int(); + + let as_int = env.builder.build_ptr_to_int(pointer, ptr_int, "to_int"); + + let tag_id_intval = ptr_int.const_int(tag_id as u64, false); + let combined = env.builder.build_or(as_int, tag_id_intval, "store_tag_id"); + + env.builder + .build_int_to_ptr(combined, pointer.get_type(), "to_ptr") +} + +pub fn tag_pointer_read_tag_id<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + pointer: PointerValue<'ctx>, +) -> IntValue<'ctx> { + let mask: u64 = 0b0000_0111; + + let ptr_int = env.ptr_int(); + + let as_int = env.builder.build_ptr_to_int(pointer, ptr_int, "to_int"); + let mask_intval = env.ptr_int().const_int(mask, false); + + let masked = env.builder.build_and(as_int, mask_intval, "mask"); + + env.builder + .build_int_cast(masked, env.context.i8_type(), "to_u8") +} + +pub fn tag_pointer_clear_tag_id<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + pointer: PointerValue<'ctx>, +) -> PointerValue<'ctx> { + let ptr_int = env.ptr_int(); + + let as_int = env.builder.build_ptr_to_int(pointer, ptr_int, "to_int"); + + let mask = { + let a = env.ptr_int().const_all_ones(); + let tag_id_bits = env.ptr_int().const_int(3, false); + env.builder.build_left_shift(a, tag_id_bits, "make_mask") + }; + + let masked = env.builder.build_and(as_int, mask, "masked"); + + env.builder + .build_int_to_ptr(masked, pointer.get_type(), "to_ptr") +} + +fn allocate_tag<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + parent: FunctionValue<'ctx>, + reuse_allocation: Option>, + union_layout: &UnionLayout<'a>, + tags: &[&[Layout<'a>]], +) -> PointerValue<'ctx> { + match reuse_allocation { + Some(ptr) => { + // check if its a null pointer + let is_null_ptr = env.builder.build_is_null(ptr, "is_null_ptr"); + let ctx = env.context; + let then_block = ctx.append_basic_block(parent, "then_allocate_fresh"); + let else_block = ctx.append_basic_block(parent, "else_reuse"); + let cont_block = ctx.append_basic_block(parent, "cont"); + + env.builder + .build_conditional_branch(is_null_ptr, then_block, else_block); + + let raw_ptr = { + env.builder.position_at_end(then_block); + let raw_ptr = + reserve_with_refcount_union_as_block_of_memory(env, *union_layout, tags); + env.builder.build_unconditional_branch(cont_block); + raw_ptr + }; + + let reuse_ptr = { + env.builder.position_at_end(else_block); + + let cleared = tag_pointer_clear_tag_id(env, ptr); + + env.builder.build_unconditional_branch(cont_block); + + cleared + }; + + { + env.builder.position_at_end(cont_block); + let phi = env.builder.build_phi(raw_ptr.get_type(), "branch"); + + phi.add_incoming(&[(&raw_ptr, then_block), (&reuse_ptr, else_block)]); + + phi.as_basic_value().into_pointer_value() + } + } + None => reserve_with_refcount_union_as_block_of_memory(env, *union_layout, tags), + } +} + pub fn get_tag_id<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, @@ -1609,7 +1789,15 @@ pub fn get_tag_id<'a, 'ctx, 'env>( get_tag_id_non_recursive(env, tag) } - UnionLayout::Recursive(_) => get_tag_id_wrapped(env, argument.into_pointer_value()), + UnionLayout::Recursive(_) => { + let argument_ptr = argument.into_pointer_value(); + + if union_layout.stores_tag_id_as_data(env.ptr_bytes) { + get_tag_id_wrapped(env, argument_ptr) + } else { + tag_pointer_read_tag_id(env, argument_ptr) + } + } UnionLayout::NonNullableUnwrapped(_) => tag_id_int_type.const_zero(), UnionLayout::NullableWrapped { nullable_id, .. } => { let argument_ptr = argument.into_pointer_value(); @@ -1634,7 +1822,12 @@ pub fn get_tag_id<'a, 'ctx, 'env>( { env.builder.position_at_end(else_block); - let tag_id = get_tag_id_wrapped(env, argument_ptr); + + let tag_id = if union_layout.stores_tag_id_as_data(env.ptr_bytes) { + get_tag_id_wrapped(env, argument_ptr) + } else { + tag_pointer_read_tag_id(env, argument_ptr) + }; env.builder.build_store(result, tag_id); env.builder.build_unconditional_branch(cont_block); } @@ -1661,11 +1854,11 @@ pub fn get_tag_id<'a, 'ctx, 'env>( fn lookup_at_index_ptr<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, + union_layout: &UnionLayout<'a>, field_layouts: &[Layout<'_>], index: usize, value: PointerValue<'ctx>, struct_type: StructType<'ctx>, - structure_layout: &Layout<'_>, ) -> BasicValueEnum<'ctx> { let builder = env.builder; @@ -1687,11 +1880,13 @@ fn lookup_at_index_ptr<'a, 'ctx, 'env>( if let Some(Layout::RecursivePointer) = field_layouts.get(index as usize) { // a recursive field is stored as a `i64*`, to use it we must cast it to // a pointer to the block of memory representation + let actual_type = basic_type_from_layout(env, &Layout::Union(*union_layout)); + debug_assert!(actual_type.is_pointer_type()); + builder.build_bitcast( result, - block_of_memory(env.context, structure_layout, env.ptr_bytes) - .ptr_type(AddressSpace::Generic), - "cast_rec_pointer_lookup_at_index_ptr", + actual_type, + "cast_rec_pointer_lookup_at_index_ptr_old", ) } else { result @@ -1700,6 +1895,7 @@ fn lookup_at_index_ptr<'a, 'ctx, 'env>( fn lookup_at_index_ptr2<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, + union_layout: &UnionLayout<'a>, tag_id_type: IntType<'ctx>, field_layouts: &[Layout<'_>], index: usize, @@ -1737,17 +1933,13 @@ fn lookup_at_index_ptr2<'a, 'ctx, 'env>( // a recursive field is stored as a `i64*`, to use it we must cast it to // a pointer to the block of memory representation - let tags = &[field_layouts]; - let struct_type = block_of_memory_slices(env.context, tags, env.ptr_bytes); - - let opaque_wrapper_type = env - .context - .struct_type(&[struct_type, tag_id_type.into()], false); + let actual_type = basic_type_from_layout(env, &Layout::Union(*union_layout)); + debug_assert!(actual_type.is_pointer_type()); builder.build_bitcast( result, - opaque_wrapper_type.ptr_type(AddressSpace::Generic), - "cast_rec_pointer_lookup_at_index_ptr", + actual_type, + "cast_rec_pointer_lookup_at_index_ptr_new", ) } else { result @@ -1771,9 +1963,11 @@ fn reserve_with_refcount_union_as_block_of_memory<'a, 'ctx, 'env>( union_layout: UnionLayout<'a>, fields: &[&[Layout<'a>]], ) -> PointerValue<'ctx> { + let ptr_bytes = env.ptr_bytes; + let block_type = block_of_memory_slices(env.context, fields, env.ptr_bytes); - let basic_type = if union_layout.stores_tag_id() { + let basic_type = if union_layout.stores_tag_id_as_data(ptr_bytes) { let tag_id_type = basic_type_from_layout(env, &union_layout.tag_id_layout()); env.context @@ -1789,7 +1983,7 @@ fn reserve_with_refcount_union_as_block_of_memory<'a, 'ctx, 'env>( .max() .unwrap_or_default(); - if union_layout.stores_tag_id() { + if union_layout.stores_tag_id_as_data(ptr_bytes) { stack_size += union_layout.tag_id_layout().stack_size(env.ptr_bytes); } @@ -1970,91 +2164,6 @@ fn list_literal<'a, 'ctx, 'env>( ) } -#[allow(clippy::too_many_arguments)] -fn invoke_roc_function<'a, 'ctx, 'env>( - env: &Env<'a, 'ctx, 'env>, - layout_ids: &mut LayoutIds<'a>, - func_spec_solutions: &FuncSpecSolutions, - scope: &mut Scope<'a, 'ctx>, - parent: FunctionValue<'ctx>, - symbol: Symbol, - layout: Layout<'a>, - function_value: FunctionValue<'ctx>, - arguments: &[Symbol], - closure_argument: Option>, - pass: &'a roc_mono::ir::Stmt<'a>, - fail: &'a roc_mono::ir::Stmt<'a>, - exception_id: ExceptionId, -) -> BasicValueEnum<'ctx> { - let context = env.context; - - let mut arg_vals: Vec = Vec::with_capacity_in(arguments.len(), env.arena); - - for arg in arguments.iter() { - arg_vals.push(load_symbol(scope, arg)); - } - arg_vals.extend(closure_argument); - - let pass_block = context.append_basic_block(parent, "invoke_pass"); - let fail_block = context.append_basic_block(parent, "invoke_fail"); - - let call_result = { - let call = env.builder.build_invoke( - function_value, - arg_vals.as_slice(), - pass_block, - fail_block, - "tmp", - ); - - call.set_call_convention(function_value.get_call_conventions()); - - call.try_as_basic_value() - .left() - .unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer.")) - }; - - { - env.builder.position_at_end(pass_block); - - scope.insert(symbol, (layout, call_result)); - - build_exp_stmt(env, layout_ids, func_spec_solutions, scope, parent, pass); - - scope.remove(&symbol); - } - - { - env.builder.position_at_end(fail_block); - - let landing_pad_type = { - let exception_ptr = context.i8_type().ptr_type(AddressSpace::Generic).into(); - let selector_value = context.i32_type().into(); - - context.struct_type(&[exception_ptr, selector_value], false) - }; - - let personality_function = get_gxx_personality_v0(env); - - let exception_object = env.builder.build_landing_pad( - landing_pad_type, - personality_function, - &[], - true, - "invoke_landing_pad", - ); - - scope.insert( - exception_id.into_inner(), - (Layout::Struct(&[]), exception_object), - ); - - build_exp_stmt(env, layout_ids, func_spec_solutions, scope, parent, fail); - } - - call_result -} - fn decrement_with_size_check<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, @@ -2089,7 +2198,6 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( parent: FunctionValue<'ctx>, stmt: &roc_mono::ir::Stmt<'a>, ) -> BasicValueEnum<'ctx> { - use roc_mono::ir::Expr; use roc_mono::ir::Stmt::*; match stmt { @@ -2116,7 +2224,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( scope, parent, layout, - &expr, + expr, ); // Make a new scope which includes the binding we just encountered. @@ -2150,95 +2258,6 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( value } - Invoke { - symbol, - call, - layout, - pass, - fail: roc_mono::ir::Stmt::Resume(_), - exception_id: _, - } => { - // when the fail case is just Rethrow, there is no cleanup work to do - // so we can just treat this invoke as a normal call - let stmt = roc_mono::ir::Stmt::Let(*symbol, Expr::Call(call.clone()), *layout, pass); - build_exp_stmt(env, layout_ids, func_spec_solutions, scope, parent, &stmt) - } - - Invoke { - symbol, - call, - layout, - pass, - fail, - exception_id, - } => match call.call_type { - CallType::ByName { - name, - arg_layouts, - ref ret_layout, - specialization_id, - .. - } => { - let bytes = specialization_id.to_bytes(); - let callee_var = CalleeSpecVar(&bytes); - let func_spec = func_spec_solutions.callee_spec(callee_var).unwrap(); - - let function_value = - function_value_by_func_spec(env, func_spec, name, arg_layouts, ret_layout); - - invoke_roc_function( - env, - layout_ids, - func_spec_solutions, - scope, - parent, - *symbol, - *layout, - function_value, - call.arguments, - None, - pass, - fail, - *exception_id, - ) - } - - CallType::Foreign { - ref foreign_symbol, - ref ret_layout, - } => build_foreign_symbol( - env, - layout_ids, - func_spec_solutions, - scope, - parent, - foreign_symbol, - call.arguments, - ret_layout, - ForeignCallOrInvoke::Invoke { - symbol: *symbol, - pass, - fail, - exception_id: *exception_id, - }, - ), - - CallType::LowLevel { .. } => { - unreachable!("lowlevel itself never throws exceptions") - } - - CallType::HigherOrderLowLevel { .. } => { - unreachable!("lowlevel itself never throws exceptions") - } - }, - - Resume(exception_id) => { - let exception_object = scope.get(&exception_id.into_inner()).unwrap().1; - env.builder.build_resume(exception_object); - - env.context.i64_type().const_zero().into() - } - Switch { branches, default_branch, @@ -2246,7 +2265,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( cond_layout, cond_symbol, } => { - let ret_type = basic_type_from_layout(env, &ret_layout); + let ret_type = basic_type_from_layout(env, ret_layout); let switch_args = SwitchArgsIr { cond_layout: *cond_layout, @@ -2324,7 +2343,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( ); // remove this join point again - scope.join_points.remove(&id); + scope.join_points.remove(id); cont_block.move_after(phi_block).unwrap(); @@ -2412,11 +2431,26 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( _ if layout.is_refcounted() => { if value.is_pointer_value() { - // BasicValueEnum::PointerValue(value_ptr) => { let value_ptr = value.into_pointer_value(); - let refcount_ptr = - PointerToRefcount::from_ptr_to_data(env, value_ptr); - refcount_ptr.decrement(env, layout); + + let then_block = env.context.append_basic_block(parent, "then"); + let done_block = env.context.append_basic_block(parent, "done"); + + let condition = + env.builder.build_is_not_null(value_ptr, "box_is_not_null"); + env.builder + .build_conditional_branch(condition, then_block, done_block); + + { + env.builder.position_at_end(then_block); + let refcount_ptr = + PointerToRefcount::from_ptr_to_data(env, value_ptr); + refcount_ptr.decrement(env, layout); + + env.builder.build_unconditional_branch(done_block); + } + + env.builder.position_at_end(done_block); } else { eprint!("we're likely leaking memory; see issue #985 for details"); } @@ -2493,17 +2527,12 @@ 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<'ctx>( - builder: &Builder<'ctx>, +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( - builder, - from_value.into(), - to_type, - "tag_to_block_of_memory", - ) + complex_bitcast_check_size(env, from_value.into(), to_type, "tag_to_block_of_memory") } pub fn cast_block_of_memory_to_tag<'ctx>( @@ -2527,34 +2556,126 @@ pub fn complex_bitcast<'ctx>( to_type: BasicTypeEnum<'ctx>, name: &str, ) -> BasicValueEnum<'ctx> { - // builder.build_bitcast(from_value, to_type, "cast_basic_basic") - // because this does not allow some (valid) bitcasts - use BasicTypeEnum::*; - match (from_value.get_type(), to_type) { - (PointerType(_), PointerType(_)) => { - // we can't use the more straightforward bitcast in all cases - // it seems like a bitcast only works on integers and pointers - // and crucially does not work not on arrays - builder.build_bitcast(from_value, to_type, name) - } - _ => { - // store the value in memory - let argument_pointer = builder.build_alloca(from_value.get_type(), "cast_alloca"); - builder.build_store(argument_pointer, from_value); - // then read it back as a different type - let to_type_pointer = builder - .build_bitcast( - argument_pointer, - to_type.ptr_type(inkwell::AddressSpace::Generic), - name, - ) - .into_pointer_value(); - - builder.build_load(to_type_pointer, "cast_value") - } + if let (PointerType(_), PointerType(_)) = (from_value.get_type(), to_type) { + // we can't use the more straightforward bitcast in all cases + // it seems like a bitcast only works on integers and pointers + // and crucially does not work not on arrays + return builder.build_bitcast(from_value, to_type, name); } + + complex_bitcast_from_bigger_than_to(builder, from_value, to_type, name) +} + +/// Check the size of the input and output types. Pretending we have more bytes at a pointer than +/// we actually do can lead to faulty optimizations and weird segfaults/crashes +fn complex_bitcast_check_size<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + from_value: BasicValueEnum<'ctx>, + to_type: BasicTypeEnum<'ctx>, + name: &str, +) -> BasicValueEnum<'ctx> { + use BasicTypeEnum::*; + + if let (PointerType(_), PointerType(_)) = (from_value.get_type(), to_type) { + // we can't use the more straightforward bitcast in all cases + // it seems like a bitcast only works on integers and pointers + // and crucially does not work not on arrays + return env.builder.build_bitcast(from_value, to_type, name); + } + + let block = env.builder.get_insert_block().expect("to be in a function"); + let parent = block.get_parent().expect("to be in a function"); + let then_block = env.context.append_basic_block(parent, "then"); + let else_block = env.context.append_basic_block(parent, "else"); + let cont_block = env.context.append_basic_block(parent, "cont"); + + let from_size = from_value.get_type().size_of().unwrap(); + let to_size = to_type.size_of().unwrap(); + + let condition = env.builder.build_int_compare( + IntPredicate::UGT, + from_size, + to_size, + "from_size >= to_size", + ); + + env.builder + .build_conditional_branch(condition, then_block, else_block); + + let then_answer = { + env.builder.position_at_end(then_block); + let result = complex_bitcast_from_bigger_than_to(env.builder, from_value, to_type, name); + env.builder.build_unconditional_branch(cont_block); + result + }; + + let else_answer = { + env.builder.position_at_end(else_block); + let result = complex_bitcast_to_bigger_than_from(env.builder, from_value, to_type, name); + env.builder.build_unconditional_branch(cont_block); + result + }; + + env.builder.position_at_end(cont_block); + + let result = env.builder.build_phi(then_answer.get_type(), "answer"); + + result.add_incoming(&[(&then_answer, then_block), (&else_answer, else_block)]); + + result.as_basic_value() +} + +fn complex_bitcast_from_bigger_than_to<'ctx>( + builder: &Builder<'ctx>, + from_value: BasicValueEnum<'ctx>, + to_type: BasicTypeEnum<'ctx>, + name: &str, +) -> BasicValueEnum<'ctx> { + // store the value in memory + let argument_pointer = builder.build_alloca(from_value.get_type(), "cast_alloca"); + builder.build_store(argument_pointer, from_value); + + // then read it back as a different type + let to_type_pointer = builder + .build_bitcast( + argument_pointer, + to_type.ptr_type(inkwell::AddressSpace::Generic), + name, + ) + .into_pointer_value(); + + builder.build_load(to_type_pointer, "cast_value") +} + +fn complex_bitcast_to_bigger_than_from<'ctx>( + builder: &Builder<'ctx>, + from_value: BasicValueEnum<'ctx>, + to_type: BasicTypeEnum<'ctx>, + name: &str, +) -> BasicValueEnum<'ctx> { + // reserve space in memory with the return type. This way, if the return type is bigger + // than the input type, we don't access invalid memory when later taking a pointer to + // the cast value + let storage = builder.build_alloca(to_type, "cast_alloca"); + + // then cast the pointer to our desired type + let from_type_pointer = builder + .build_bitcast( + storage, + from_value + .get_type() + .ptr_type(inkwell::AddressSpace::Generic), + name, + ) + .into_pointer_value(); + + // store the value in memory + builder.build_store(from_type_pointer, from_value); + + // then read it back as a different type + builder.build_load(storage, "cast_value") } /// get the tag id out of a pointer to a wrapped (i.e. stores the tag id at runtime) layout @@ -2659,7 +2780,7 @@ fn build_switch_ir<'a, 'ctx, 'env>( .into_int_value() } Layout::Union(variant) => { - cond_layout = Layout::Builtin(Builtin::Int64); + cond_layout = variant.tag_id_layout(); get_tag_id(env, parent, &variant, cond_value) } @@ -2829,7 +2950,7 @@ fn expose_function_to_host<'a, 'ctx, 'env>( roc_function: FunctionValue<'ctx>, ) { // Assumption: there is only one specialization of a host-exposed function - let ident_string = symbol.ident_string(&env.interns); + let ident_string = symbol.as_str(&env.interns); let c_function_name: String = format!("roc__{}_1_exposed", ident_string); expose_function_to_host_help(env, ident_string, roc_function, &c_function_name); @@ -2837,17 +2958,23 @@ fn expose_function_to_host<'a, 'ctx, 'env>( fn expose_function_to_host_help<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, - ident_string: &inlinable_string::InlinableString, + ident_string: &str, roc_function: FunctionValue<'ctx>, c_function_name: &str, ) -> FunctionValue<'ctx> { - let roc_wrapper_function = make_exception_catcher(env, roc_function); + let context = env.context; - let roc_function_type = roc_wrapper_function.get_type(); + let wrapper_return_type = context.struct_type( + &[ + context.i64_type().into(), + roc_function.get_type().get_return_type().unwrap(), + ], + false, + ); // STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}` - let mut argument_types = roc_function_type.get_param_types(); - let return_type = roc_function_type.get_return_type().unwrap(); + let mut argument_types = roc_function.get_type().get_param_types(); + let return_type = wrapper_return_type; let output_type = return_type.ptr_type(AddressSpace::Generic); argument_types.push(output_type.into()); @@ -2880,12 +3007,28 @@ fn expose_function_to_host_help<'a, 'ctx, 'env>( let args = &args[..args.len() - 1]; debug_assert_eq!(args.len(), roc_function.get_params().len()); - debug_assert_eq!(args.len(), roc_wrapper_function.get_params().len()); - let call_wrapped = builder.build_call(roc_wrapper_function, args, "call_wrapped_function"); - call_wrapped.set_call_convention(FAST_CALL_CONV); + let call_result = { + if env.is_gen_test { + let roc_wrapper_function = make_exception_catcher(env, roc_function); + debug_assert_eq!(args.len(), roc_wrapper_function.get_params().len()); - let call_result = call_wrapped.try_as_basic_value().left().unwrap(); + builder.position_at_end(entry); + + let call_wrapped = + builder.build_call(roc_wrapper_function, args, "call_wrapped_function"); + call_wrapped.set_call_convention(FAST_CALL_CONV); + + call_wrapped.try_as_basic_value().left().unwrap() + } else { + let call_unwrapped = builder.build_call(roc_function, args, "call_unwrapped_function"); + call_unwrapped.set_call_convention(FAST_CALL_CONV); + + let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap(); + + make_good_roc_result(env, call_unwrapped_result) + } + }; let output_arg = c_function .get_nth_param(output_arg_index as u32) @@ -2923,7 +3066,41 @@ fn expose_function_to_host_help<'a, 'ctx, 'env>( c_function } -fn invoke_and_catch<'a, 'ctx, 'env, F, T>( +pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> { + let type_ = env.context.i8_type().array_type(5 * env.ptr_bytes); + + let global = match env.module.get_global("roc_sjlj_buffer") { + Some(global) => global, + None => env.module.add_global(type_, None, "roc_sjlj_buffer"), + }; + + global.set_initializer(&type_.const_zero()); + + env.builder + .build_bitcast( + global.as_pointer_value(), + env.context.i8_type().ptr_type(AddressSpace::Generic), + "cast_sjlj_buffer", + ) + .into_pointer_value() +} + +pub fn get_sjlj_message_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> { + let type_ = env.context.i8_type().ptr_type(AddressSpace::Generic); + + let global = match env.module.get_global("roc_sjlj_message_buffer") { + Some(global) => global, + None => env + .module + .add_global(type_, None, "roc_sjlj_message_buffer"), + }; + + global.set_initializer(&type_.const_zero()); + + global.as_pointer_value() +} + +fn set_jump_and_catch_long_jump<'a, 'ctx, 'env, F, T>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, function: F, @@ -2943,145 +3120,145 @@ where false, ); + let result_alloca = builder.build_alloca(call_result_type, "result"); + let then_block = context.append_basic_block(parent, "then_block"); let catch_block = context.append_basic_block(parent, "catch_block"); let cont_block = context.append_basic_block(parent, "cont_block"); - let result_alloca = builder.build_alloca(call_result_type, "result"); + let buffer = get_sjlj_buffer(env); - // invoke instead of call, so that we can catch any exeptions thrown in Roc code - let call_result = { - let call = builder.build_invoke( - function, - &arguments, - then_block, - catch_block, - "call_roc_function", - ); - call.set_call_convention(calling_convention); - call.try_as_basic_value().left().unwrap() - }; - - // exception handling - { - builder.position_at_end(catch_block); - - build_catch_all_landing_pad(env, result_alloca); - - builder.build_unconditional_branch(cont_block); - } - - { - builder.position_at_end(then_block); - - let return_value = { - let v1 = call_result_type.const_zero(); - - let v2 = builder - .build_insert_value(v1, context.i64_type().const_zero(), 0, "set_no_error") - .unwrap(); - let v3 = builder - .build_insert_value(v2, call_result, 1, "set_call_result") - .unwrap(); - - v3 - }; - - let ptr = builder.build_bitcast( - result_alloca, - call_result_type.ptr_type(AddressSpace::Generic), - "name", - ); - builder.build_store(ptr.into_pointer_value(), return_value); - - builder.build_unconditional_branch(cont_block); - } - - builder.position_at_end(cont_block); - - builder.build_load(result_alloca, "result") -} - -fn build_catch_all_landing_pad<'a, 'ctx, 'env>( - env: &Env<'a, 'ctx, 'env>, - result_alloca: PointerValue<'ctx>, -) { - let context = env.context; - let builder = env.builder; - - let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic); - - let landing_pad_type = { - let exception_ptr = context.i8_type().ptr_type(AddressSpace::Generic).into(); - let selector_value = context.i32_type().into(); - - context.struct_type(&[exception_ptr, selector_value], false) - }; - - // null pointer functions as a catch-all catch clause - let null = u8_ptr.const_zero(); - - let personality_function = get_gxx_personality_v0(env); - - let info = builder - .build_landing_pad( - landing_pad_type, - personality_function, - &[null.into()], - false, - "main_landing_pad", - ) - .into_struct_value(); - - let exception_ptr = builder - .build_extract_value(info, 0, "exception_ptr") - .unwrap(); - - let thrown = cxa_begin_catch(env, exception_ptr); - - let error_msg = { - let exception_type = u8_ptr; - let ptr = builder.build_bitcast( - thrown, - exception_type.ptr_type(AddressSpace::Generic), - "cast", - ); - - builder.build_load(ptr.into_pointer_value(), "error_msg") - }; - - let return_type = context.struct_type(&[context.i64_type().into(), u8_ptr.into()], false); - - let return_value = { - let v1 = return_type.const_zero(); - - // flag is non-zero, indicating failure - let flag = context.i64_type().const_int(1, false); - - let v2 = builder - .build_insert_value(v1, flag, 0, "set_error") - .unwrap(); - - let v3 = builder - .build_insert_value(v2, error_msg, 1, "set_exception") - .unwrap(); - - v3 - }; - - // bitcast result alloca so we can store our concrete type { flag, error_msg } in there - let result_alloca_bitcast = builder + let cast = env + .builder .build_bitcast( - result_alloca, - return_type.ptr_type(AddressSpace::Generic), - "result_alloca_bitcast", + buffer, + env.context + .i8_type() + .ptr_type(AddressSpace::Generic) + .array_type(5) + .ptr_type(AddressSpace::Generic), + "to [5 x i8*]", ) .into_pointer_value(); - // store our return value - builder.build_store(result_alloca_bitcast, return_value); + let zero = env.context.i32_type().const_zero(); - cxa_end_catch(env); + let index = env.context.i32_type().const_zero(); + let fa = unsafe { + env.builder + .build_in_bounds_gep(cast, &[zero, index], "name") + }; + + let index = env.context.i32_type().const_int(2, false); + let ss = unsafe { + env.builder + .build_in_bounds_gep(cast, &[zero, index], "name") + }; + + let index = env.context.i32_type().const_int(3, false); + let error_msg = unsafe { + env.builder + .build_in_bounds_gep(cast, &[zero, index], "name") + }; + + let frame_address = env.call_intrinsic( + LLVM_FRAME_ADDRESS, + &[env.context.i32_type().const_zero().into()], + ); + + env.builder.build_store(fa, frame_address); + + let stack_save = env.call_intrinsic(LLVM_STACK_SAVE, &[]); + + env.builder.build_store(ss, stack_save); + + let panicked_u32 = env.call_intrinsic(LLVM_SETJMP, &[buffer.into()]); + let panicked_bool = env.builder.build_int_compare( + IntPredicate::NE, + panicked_u32.into_int_value(), + panicked_u32.get_type().into_int_type().const_zero(), + "to_bool", + ); + + env.builder + .build_conditional_branch(panicked_bool, catch_block, then_block); + + // all went well + { + builder.position_at_end(then_block); + + let call = env.builder.build_call(function, arguments, "call_function"); + + call.set_call_convention(calling_convention); + + let call_result = call.try_as_basic_value().left().unwrap(); + + let return_value = make_good_roc_result(env, call_result); + + builder.build_store(result_alloca, return_value); + + env.builder.build_unconditional_branch(cont_block); + } + + // something went wrong + { + builder.position_at_end(catch_block); + + let error_msg = { + // u8** + let ptr_int_ptr = builder.build_bitcast( + error_msg, + env.context + .i8_type() + .ptr_type(AddressSpace::Generic) + .ptr_type(AddressSpace::Generic), + "cast", + ); + + // u8* again + let ptr_int = builder.build_load(ptr_int_ptr.into_pointer_value(), "ptr_int"); + + ptr_int + }; + + let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic); + let return_type = context.struct_type(&[context.i64_type().into(), u8_ptr.into()], false); + // let return_type = call_result_type; + + let return_value = { + let v1 = return_type.const_zero(); + + // flag is non-zero, indicating failure + let flag = context.i64_type().const_int(1, false); + + let v2 = builder + .build_insert_value(v1, flag, 0, "set_error") + .unwrap(); + + let v3 = builder + .build_insert_value(v2, error_msg, 1, "set_exception") + .unwrap(); + v3 + }; + + // bitcast result alloca so we can store our concrete type { flag, error_msg } in there + let result_alloca_bitcast = builder + .build_bitcast( + result_alloca, + return_type.ptr_type(AddressSpace::Generic), + "result_alloca_bitcast", + ) + .into_pointer_value(); + + // store our return value + builder.build_store(result_alloca_bitcast, return_value); + + env.builder.build_unconditional_branch(cont_block); + } + + env.builder.position_at_end(cont_block); + + builder.build_load(result_alloca, "load_result") } fn make_exception_catcher<'a, 'ctx, 'env>( @@ -3097,6 +3274,30 @@ fn make_exception_catcher<'a, 'ctx, 'env>( function_value } +fn make_good_roc_result<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + return_value: BasicValueEnum<'ctx>, +) -> BasicValueEnum<'ctx> { + let context = env.context; + let builder = env.builder; + + let content_type = return_value.get_type(); + let wrapper_return_type = + context.struct_type(&[context.i64_type().into(), content_type], false); + + let v1 = wrapper_return_type.const_zero(); + + let v2 = builder + .build_insert_value(v1, context.i64_type().const_zero(), 0, "set_no_error") + .unwrap(); + + let v3 = builder + .build_insert_value(v2, return_value, 1, "set_call_result") + .unwrap(); + + v3.into_struct_value().into() +} + fn make_exception_catching_wrapper<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, roc_function: FunctionValue<'ctx>, @@ -3113,17 +3314,20 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>( let wrapper_return_type = context.struct_type( &[ context.i64_type().into(), - roc_function_type.get_return_type().unwrap(), + roc_function.get_type().get_return_type().unwrap(), ], false, ); + // argument_types.push(wrapper_return_type.ptr_type(AddressSpace::Generic).into()); + + // let wrapper_function_type = env.context.void_type().fn_type(&argument_types, false); let wrapper_function_type = wrapper_return_type.fn_type(&argument_types, false); // Add main to the module. let wrapper_function = add_func( env.module, - &wrapper_function_name, + wrapper_function_name, wrapper_function_type, Linkage::External, C_CALL_CONV, @@ -3143,7 +3347,7 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>( debug_info_init!(env, wrapper_function); - let result = invoke_and_catch( + let result = set_jump_and_catch_long_jump( env, wrapper_function, roc_function, @@ -3246,7 +3450,7 @@ fn build_procedures_help<'a, 'ctx, 'env>( // Add all the Proc headers to the module. // We have to do this in a separate pass first, // because their bodies may reference each other. - let headers = build_proc_headers(env, &mod_solutions, procedures, &mut scope); + let headers = build_proc_headers(env, mod_solutions, procedures, &mut scope); let (_, function_pass) = construct_optimization_passes(env.module, opt_level); @@ -3260,7 +3464,7 @@ fn build_procedures_help<'a, 'ctx, 'env>( current_scope.retain_top_level_thunks_for_module(home); build_proc( - &env, + env, mod_solutions, &mut layout_ids, func_spec_solutions, @@ -3273,7 +3477,7 @@ fn build_procedures_help<'a, 'ctx, 'env>( env.dibuilder.finalize(); if fn_val.verify(true) { - function_pass.run_on(&fn_val); + function_pass.run_on(fn_val); } else { let mode = "NON-OPTIMIZED"; @@ -3317,7 +3521,7 @@ fn func_spec_name<'a>( let mut buf = bumpalo::collections::String::with_capacity_in(1, arena); - let ident_string = symbol.ident_string(interns); + let ident_string = symbol.as_str(interns); let module_string = interns.module_ids.get_name(symbol.module_id()).unwrap(); write!(buf, "{}_{}_", module_string, ident_string).unwrap(); @@ -3343,7 +3547,7 @@ fn build_proc_header<'a, 'ctx, 'env>( let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena); for (layout, _) in args.iter() { - let arg_type = basic_type_from_layout(env, &layout); + let arg_type = basic_type_from_layout(env, layout); arg_basic_types.push(arg_type); } @@ -3377,18 +3581,6 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( lambda_set: LambdaSet<'a>, result: &Layout<'a>, ) { - let context = &env.context; - let builder = env.builder; - - // STEP 1: build function header - - // e.g. `roc__main_1_Fx_caller` - let function_name = format!( - "roc__{}_{}_caller", - def_name, - alias_symbol.ident_string(&env.interns) - ); - let mut argument_types = Vec::with_capacity_in(arguments.len() + 3, env.arena); for layout in arguments { @@ -3405,6 +3597,9 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( }; argument_types.push(closure_argument_type.into()); + let context = &env.context; + let builder = env.builder; + let result_type = basic_type_from_layout(env, result); let roc_call_result_type = @@ -3413,6 +3608,15 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( let output_type = { roc_call_result_type.ptr_type(AddressSpace::Generic) }; argument_types.push(output_type.into()); + // STEP 1: build function header + + // e.g. `roc__main_1_Fx_caller` + let function_name = format!( + "roc__{}_{}_caller", + def_name, + alias_symbol.as_str(&env.interns) + ); + let function_type = context.void_type().fn_type(&argument_types, false); let function_value = add_func( @@ -3431,9 +3635,15 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( let mut parameters = function_value.get_params(); let output = parameters.pop().unwrap().into_pointer_value(); - let closure_data_ptr = parameters.pop().unwrap().into_pointer_value(); - let closure_data = builder.build_load(closure_data_ptr, "load_closure_data"); + let closure_data = if let Some(closure_data_ptr) = parameters.pop() { + let closure_data = + builder.build_load(closure_data_ptr.into_pointer_value(), "load_closure_data"); + + env.arena.alloc([closure_data]) as &[_] + } else { + &[] + }; let mut parameters = parameters; @@ -3442,12 +3652,12 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( *param = builder.build_load(param.into_pointer_value(), "load_param"); } - let call_result = invoke_and_catch( + let call_result = set_jump_and_catch_long_jump( env, function_value, evaluator, evaluator.get_call_conventions(), - &[closure_data], + closure_data, result_type, ); @@ -3465,8 +3675,12 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( ); // STEP 4: build a {} -> u64 function that gives the size of the closure - let layout = lambda_set.runtime_representation(); - build_host_exposed_alias_size(env, def_name, alias_symbol, layout); + build_host_exposed_alias_size( + env, + def_name, + alias_symbol, + lambda_set.runtime_representation(), + ); } fn build_host_exposed_alias_size<'a, 'ctx, 'env>( @@ -3499,14 +3713,14 @@ fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>( format!( "roc__{}_{}_{}_size", def_name, - alias_symbol.ident_string(&env.interns), + alias_symbol.as_str(&env.interns), label ) } else { format!( "roc__{}_{}_size", def_name, - alias_symbol.ident_string(&env.interns) + alias_symbol.as_str(&env.interns) ) }; @@ -3573,7 +3787,7 @@ pub fn build_proc<'a, 'ctx, 'env>( &top_level.result, ); - let ident_string = proc.name.ident_string(&env.interns); + let ident_string = proc.name.as_str(&env.interns); let fn_name: String = format!("{}_1", ident_string); build_closure_caller( @@ -3602,7 +3816,7 @@ pub fn build_proc<'a, 'ctx, 'env>( // Add args to scope for (arg_val, (layout, arg_symbol)) in fn_val.get_param_iter().zip(args) { - arg_val.set_name(arg_symbol.ident_string(&env.interns)); + arg_val.set_name(arg_symbol.as_str(&env.interns)); scope.insert(*arg_symbol, (*layout, arg_val)); } @@ -4249,8 +4463,8 @@ fn run_low_level<'a, 'ctx, 'env>( str_starts_with(env, scope, args[0], args[1]) } - StrStartsWithCodePoint => { - // Str.startsWithCodePoint : Str, U32 -> Bool + StrStartsWithCodePt => { + // Str.startsWithCodePt : Str, U32 -> Bool debug_assert_eq!(args.len(), 2); str_starts_with_code_point(env, scope, args[0], args[1]) @@ -4281,7 +4495,15 @@ fn run_low_level<'a, 'ctx, 'env>( str_from_utf8(env, parent, original_wrapper) } - StrToBytes => { + StrFromUtf8Range => { + debug_assert_eq!(args.len(), 2); + + let list_wrapper = load_symbol(scope, &args[0]).into_struct_value(); + let count_and_start = load_symbol(scope, &args[1]).into_struct_value(); + + str_from_utf8_range(env, parent, list_wrapper, count_and_start) + } + StrToUtf8 => { // Str.fromInt : Str -> List U8 debug_assert_eq!(args.len(), 1); @@ -4289,7 +4511,7 @@ fn run_low_level<'a, 'ctx, 'env>( // we just implement it here to subvert the type system let string = load_symbol(scope, &args[0]); - str_to_bytes(env, string.into_struct_value()) + str_to_utf8(env, string.into_struct_value()) } StrSplit => { // Str.split : Str, Str -> List Str @@ -4481,6 +4703,42 @@ fn run_low_level<'a, 'ctx, 'env>( } } } + NumBytesToU16 => { + debug_assert_eq!(args.len(), 2); + let list = load_symbol(scope, &args[0]).into_struct_value(); + let position = load_symbol(scope, &args[1]); + call_bitcode_fn( + env, + &[ + complex_bitcast( + env.builder, + list.into(), + env.context.i128_type().into(), + "to_i128", + ), + position, + ], + bitcode::NUM_BYTES_TO_U16, + ) + } + NumBytesToU32 => { + debug_assert_eq!(args.len(), 2); + let list = load_symbol(scope, &args[0]).into_struct_value(); + let position = load_symbol(scope, &args[1]); + call_bitcode_fn( + env, + &[ + complex_bitcast( + env.builder, + list.into(), + env.context.i128_type().into(), + "to_i128", + ), + position, + ], + bitcode::NUM_BYTES_TO_U32, + ) + } NumCompare => { use inkwell::FloatPredicate; @@ -4912,16 +5170,6 @@ fn run_low_level<'a, 'ctx, 'env>( } } -enum ForeignCallOrInvoke<'a> { - Call, - Invoke { - symbol: Symbol, - exception_id: ExceptionId, - pass: &'a roc_mono::ir::Stmt<'a>, - fail: &'a roc_mono::ir::Stmt<'a>, - }, -} - fn build_foreign_symbol_return_result<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, scope: &mut Scope<'a, 'ctx>, @@ -4976,14 +5224,10 @@ fn build_foreign_symbol_write_result_into_ptr<'a, 'ctx, 'env>( #[allow(clippy::too_many_arguments)] fn build_foreign_symbol<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, - layout_ids: &mut LayoutIds<'a>, - func_spec_solutions: &FuncSpecSolutions, scope: &mut Scope<'a, 'ctx>, - parent: FunctionValue<'ctx>, foreign: &roc_module::ident::ForeignSymbol, arguments: &[Symbol], ret_layout: &Layout<'a>, - call_or_invoke: ForeignCallOrInvoke<'a>, ) -> BasicValueEnum<'ctx> { let ret_type = basic_type_from_layout(env, ret_layout); let return_pointer = env.builder.build_alloca(ret_type, "return_value"); @@ -4997,90 +5241,53 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( build_foreign_symbol_return_result(env, scope, foreign, arguments, ret_type) }; - match call_or_invoke { - ForeignCallOrInvoke::Call => { - let call = env.builder.build_call(function, arguments, "tmp"); + let call = env.builder.build_call(function, arguments, "tmp"); - // this is a foreign function, use c calling convention - call.set_call_convention(C_CALL_CONV); + // this is a foreign function, use c calling convention + call.set_call_convention(C_CALL_CONV); - call.try_as_basic_value(); + call.try_as_basic_value(); - if pass_result_by_pointer { - env.builder.build_load(return_pointer, "read_result") - } else { - call.try_as_basic_value().left().unwrap() - } - } - ForeignCallOrInvoke::Invoke { - symbol, - pass, - fail, - exception_id, - } => { - let pass_block = env.context.append_basic_block(parent, "invoke_pass"); - let fail_block = env.context.append_basic_block(parent, "invoke_fail"); - - let call = env - .builder - .build_invoke(function, arguments, pass_block, fail_block, "tmp"); - - // this is a foreign function, use c calling convention - call.set_call_convention(C_CALL_CONV); - - call.try_as_basic_value(); - - let call_result = if pass_result_by_pointer { - env.builder.build_load(return_pointer, "read_result") - } else { - call.try_as_basic_value().left().unwrap() - }; - - { - env.builder.position_at_end(pass_block); - - scope.insert(symbol, (*ret_layout, call_result)); - - build_exp_stmt(env, layout_ids, func_spec_solutions, scope, parent, pass); - - scope.remove(&symbol); - } - - { - env.builder.position_at_end(fail_block); - - let landing_pad_type = { - let exception_ptr = - env.context.i8_type().ptr_type(AddressSpace::Generic).into(); - let selector_value = env.context.i32_type().into(); - - env.context - .struct_type(&[exception_ptr, selector_value], false) - }; - - let personality_function = get_gxx_personality_v0(env); - - let exception_object = env.builder.build_landing_pad( - landing_pad_type, - personality_function, - &[], - true, - "invoke_landing_pad", - ); - - scope.insert( - exception_id.into_inner(), - (Layout::Struct(&[]), exception_object), - ); - - build_exp_stmt(env, layout_ids, func_spec_solutions, scope, parent, fail); - } - - call_result - } + if pass_result_by_pointer { + env.builder.build_load(return_pointer, "read_result") + } else { + call.try_as_basic_value().left().unwrap() } } +fn throw_on_overflow<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + parent: FunctionValue<'ctx>, + result: StructValue<'ctx>, // of the form { value: T, has_overflowed: bool } + message: &str, +) -> BasicValueEnum<'ctx> { + let bd = env.builder; + let context = env.context; + + let has_overflowed = bd.build_extract_value(result, 1, "has_overflowed").unwrap(); + + let condition = bd.build_int_compare( + IntPredicate::EQ, + has_overflowed.into_int_value(), + context.bool_type().const_zero(), + "has_not_overflowed", + ); + + let then_block = context.append_basic_block(parent, "then_block"); + let throw_block = context.append_basic_block(parent, "throw_block"); + + bd.build_conditional_branch(condition, then_block, throw_block); + + bd.position_at_end(throw_block); + + throw_exception(env, message); + + bd.position_at_end(then_block); + + bd.build_extract_value(result, 0, "operation_result") + .unwrap() +} + fn build_int_binop<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, @@ -5097,8 +5304,6 @@ fn build_int_binop<'a, 'ctx, 'env>( match op { NumAdd => { - let context = env.context; - let intrinsic = match lhs_layout { Layout::Builtin(Builtin::Int8) => LLVM_SADD_WITH_OVERFLOW_I8, Layout::Builtin(Builtin::Int16) => LLVM_SADD_WITH_OVERFLOW_I16, @@ -5117,34 +5322,11 @@ fn build_int_binop<'a, 'ctx, 'env>( .call_intrinsic(intrinsic, &[lhs.into(), rhs.into()]) .into_struct_value(); - let add_result = bd.build_extract_value(result, 0, "add_result").unwrap(); - let has_overflowed = bd.build_extract_value(result, 1, "has_overflowed").unwrap(); - - let condition = bd.build_int_compare( - IntPredicate::EQ, - has_overflowed.into_int_value(), - context.bool_type().const_zero(), - "has_not_overflowed", - ); - - let then_block = context.append_basic_block(parent, "then_block"); - let throw_block = context.append_basic_block(parent, "throw_block"); - - bd.build_conditional_branch(condition, then_block, throw_block); - - bd.position_at_end(throw_block); - - throw_exception(env, "integer addition overflowed!"); - - bd.position_at_end(then_block); - - add_result + throw_on_overflow(env, parent, result, "integer addition overflowed!") } NumAddWrap => bd.build_int_add(lhs, rhs, "add_int_wrap").into(), NumAddChecked => env.call_intrinsic(LLVM_SADD_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]), NumSub => { - let context = env.context; - let intrinsic = match lhs_layout { Layout::Builtin(Builtin::Int8) => LLVM_SSUB_WITH_OVERFLOW_I8, Layout::Builtin(Builtin::Int16) => LLVM_SSUB_WITH_OVERFLOW_I16, @@ -5163,59 +5345,16 @@ fn build_int_binop<'a, 'ctx, 'env>( .call_intrinsic(intrinsic, &[lhs.into(), rhs.into()]) .into_struct_value(); - let sub_result = bd.build_extract_value(result, 0, "sub_result").unwrap(); - let has_overflowed = bd.build_extract_value(result, 1, "has_overflowed").unwrap(); - - let condition = bd.build_int_compare( - IntPredicate::EQ, - has_overflowed.into_int_value(), - context.bool_type().const_zero(), - "has_not_overflowed", - ); - - let then_block = context.append_basic_block(parent, "then_block"); - let throw_block = context.append_basic_block(parent, "throw_block"); - - bd.build_conditional_branch(condition, then_block, throw_block); - - bd.position_at_end(throw_block); - - throw_exception(env, "integer subtraction overflowed!"); - - bd.position_at_end(then_block); - - sub_result + throw_on_overflow(env, parent, result, "integer subtraction overflowed!") } NumSubWrap => bd.build_int_sub(lhs, rhs, "sub_int").into(), NumSubChecked => env.call_intrinsic(LLVM_SSUB_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]), NumMul => { - let context = env.context; let result = env .call_intrinsic(LLVM_SMUL_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]) .into_struct_value(); - let mul_result = bd.build_extract_value(result, 0, "mul_result").unwrap(); - let has_overflowed = bd.build_extract_value(result, 1, "has_overflowed").unwrap(); - - let condition = bd.build_int_compare( - IntPredicate::EQ, - has_overflowed.into_int_value(), - context.bool_type().const_zero(), - "has_not_overflowed", - ); - - let then_block = context.append_basic_block(parent, "then_block"); - let throw_block = context.append_basic_block(parent, "throw_block"); - - bd.build_conditional_branch(condition, then_block, throw_block); - - bd.position_at_end(throw_block); - - throw_exception(env, "integer multiplication overflowed!"); - - bd.position_at_end(then_block); - - mul_result + throw_on_overflow(env, parent, result, "integer multiplication overflowed!") } NumMulWrap => bd.build_int_mul(lhs, rhs, "mul_int").into(), NumMulChecked => env.call_intrinsic(LLVM_SMUL_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]), @@ -5293,7 +5432,7 @@ fn build_int_binop<'a, 'ctx, 'env>( } } NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_int").into(), - NumPowInt => call_bitcode_fn(env, &[lhs.into(), rhs.into()], &bitcode::NUM_POW_INT), + NumPowInt => call_bitcode_fn(env, &[lhs.into(), rhs.into()], bitcode::NUM_POW_INT), NumBitwiseAnd => bd.build_and(lhs, rhs, "int_bitwise_and").into(), NumBitwiseXor => bd.build_xor(lhs, rhs, "int_bitwise_xor").into(), NumBitwiseOr => bd.build_or(lhs, rhs, "int_bitwise_or").into(), @@ -5354,6 +5493,9 @@ pub fn build_num_binop<'a, 'ctx, 'env>( rhs_layout, op, ), + Decimal => { + build_dec_binop(env, parent, lhs_arg, lhs_layout, rhs_arg, rhs_layout, op) + } _ => { unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid builtin layout: ({:?})", op, lhs_layout); } @@ -5387,7 +5529,7 @@ fn build_float_binop<'a, 'ctx, 'env>( let result = bd.build_float_add(lhs, rhs, "add_float"); let is_finite = - call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE).into_int_value(); + call_bitcode_fn(env, &[result.into()], bitcode::NUM_IS_FINITE).into_int_value(); let then_block = context.append_basic_block(parent, "then_block"); let throw_block = context.append_basic_block(parent, "throw_block"); @@ -5408,7 +5550,7 @@ fn build_float_binop<'a, 'ctx, 'env>( let result = bd.build_float_add(lhs, rhs, "add_float"); let is_finite = - call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE).into_int_value(); + call_bitcode_fn(env, &[result.into()], bitcode::NUM_IS_FINITE).into_int_value(); let is_infinite = bd.build_not(is_finite, "negate"); let struct_type = context.struct_type( @@ -5436,7 +5578,7 @@ fn build_float_binop<'a, 'ctx, 'env>( let result = bd.build_float_sub(lhs, rhs, "sub_float"); let is_finite = - call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE).into_int_value(); + call_bitcode_fn(env, &[result.into()], bitcode::NUM_IS_FINITE).into_int_value(); let then_block = context.append_basic_block(parent, "then_block"); let throw_block = context.append_basic_block(parent, "throw_block"); @@ -5457,7 +5599,7 @@ fn build_float_binop<'a, 'ctx, 'env>( let result = bd.build_float_sub(lhs, rhs, "sub_float"); let is_finite = - call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE).into_int_value(); + call_bitcode_fn(env, &[result.into()], bitcode::NUM_IS_FINITE).into_int_value(); let is_infinite = bd.build_not(is_finite, "negate"); let struct_type = context.struct_type( @@ -5485,7 +5627,7 @@ fn build_float_binop<'a, 'ctx, 'env>( let result = bd.build_float_mul(lhs, rhs, "mul_float"); let is_finite = - call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE).into_int_value(); + call_bitcode_fn(env, &[result.into()], bitcode::NUM_IS_FINITE).into_int_value(); let then_block = context.append_basic_block(parent, "then_block"); let throw_block = context.append_basic_block(parent, "throw_block"); @@ -5506,7 +5648,7 @@ fn build_float_binop<'a, 'ctx, 'env>( let result = bd.build_float_mul(lhs, rhs, "mul_float"); let is_finite = - call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE).into_int_value(); + call_bitcode_fn(env, &[result.into()], bitcode::NUM_IS_FINITE).into_int_value(); let is_infinite = bd.build_not(is_finite, "negate"); let struct_type = context.struct_type( @@ -5540,6 +5682,75 @@ fn build_float_binop<'a, 'ctx, 'env>( } } +fn build_dec_binop<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + parent: FunctionValue<'ctx>, + lhs: BasicValueEnum<'ctx>, + _lhs_layout: &Layout<'a>, + rhs: BasicValueEnum<'ctx>, + _rhs_layout: &Layout<'a>, + op: LowLevel, +) -> BasicValueEnum<'ctx> { + use roc_module::low_level::LowLevel::*; + + match op { + NumAddChecked => call_bitcode_fn(env, &[lhs, rhs], bitcode::DEC_ADD_WITH_OVERFLOW), + NumSubChecked => call_bitcode_fn(env, &[lhs, rhs], bitcode::DEC_SUB_WITH_OVERFLOW), + NumMulChecked => call_bitcode_fn(env, &[lhs, rhs], bitcode::DEC_MUL_WITH_OVERFLOW), + NumAdd => build_dec_binop_throw_on_overflow( + env, + parent, + bitcode::DEC_ADD_WITH_OVERFLOW, + lhs, + rhs, + "decimal addition overflowed", + ), + NumSub => build_dec_binop_throw_on_overflow( + env, + parent, + bitcode::DEC_SUB_WITH_OVERFLOW, + lhs, + rhs, + "decimal subtraction overflowed", + ), + NumMul => build_dec_binop_throw_on_overflow( + env, + parent, + bitcode::DEC_MUL_WITH_OVERFLOW, + lhs, + rhs, + "decimal multiplication overflowed", + ), + NumDivUnchecked => call_bitcode_fn(env, &[lhs, rhs], bitcode::DEC_DIV), + _ => { + unreachable!("Unrecognized int binary operation: {:?}", op); + } + } +} + +fn build_dec_binop_throw_on_overflow<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + parent: FunctionValue<'ctx>, + operation: &str, + lhs: BasicValueEnum<'ctx>, + rhs: BasicValueEnum<'ctx>, + message: &str, +) -> BasicValueEnum<'ctx> { + let overflow_type = crate::llvm::convert::zig_with_overflow_roc_dec(env); + + let result_ptr = env.builder.build_alloca(overflow_type, "result_ptr"); + call_void_bitcode_fn(env, &[result_ptr.into(), lhs, rhs], operation); + + let result = env + .builder + .build_load(result_ptr, "load_overflow") + .into_struct_value(); + + let value = throw_on_overflow(env, parent, result, message).into_struct_value(); + + env.builder.build_extract_value(value, 0, "num").unwrap() +} + fn int_type_signed_min(int_type: IntType) -> IntValue { let width = int_type.get_bit_width(); @@ -5733,10 +5944,10 @@ fn build_float_unary_op<'a, 'ctx, 'env>( env.context.i64_type(), "num_floor", ), - NumIsFinite => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_FINITE), - NumAtan => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ATAN), - NumAcos => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ACOS), - NumAsin => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ASIN), + NumIsFinite => call_bitcode_fn(env, &[arg.into()], bitcode::NUM_IS_FINITE), + NumAtan => call_bitcode_fn(env, &[arg.into()], bitcode::NUM_ATAN), + NumAcos => call_bitcode_fn(env, &[arg.into()], bitcode::NUM_ACOS), + NumAsin => call_bitcode_fn(env, &[arg.into()], bitcode::NUM_ASIN), _ => { unreachable!("Unrecognized int unary operation: {:?}", op); } @@ -5848,131 +6059,26 @@ fn define_global_error_str<'a, 'ctx, 'env>( } fn throw_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, message: &str) { - let context = env.context; let builder = env.builder; - let info = { - // we represented both void and char pointers with `u8*` - let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); + // define the error message as a global + // (a hash is used such that the same value is not defined repeatedly) + let error_msg_global = define_global_error_str(env, message); - // allocate an exception (that can hold a pointer to a string) - let str_ptr_size = env - .context - .i64_type() - .const_int(env.ptr_bytes as u64, false); - let initial = cxa_allocate_exception(env, str_ptr_size); + let cast = env + .builder + .build_bitcast( + error_msg_global.as_pointer_value(), + env.context.i8_type().ptr_type(AddressSpace::Generic), + "cast_void", + ) + .into_pointer_value(); - // define the error message as a global - // (a hash is used such that the same value is not defined repeatedly) - let error_msg_global = define_global_error_str(env, message); - - // cast this to a void pointer - let error_msg_ptr = - builder.build_bitcast(error_msg_global.as_pointer_value(), u8_ptr, "unused"); - - // store this void pointer in the exception - let exception_type = u8_ptr; - let exception_value = error_msg_ptr; - - let temp = builder - .build_bitcast( - initial, - exception_type.ptr_type(AddressSpace::Generic), - "exception_object_str_ptr_ptr", - ) - .into_pointer_value(); - - builder.build_store(temp, exception_value); - - initial - }; - - cxa_throw_exception(env, info); + env.call_panic(cast, PanicTagId::NullTerminatedString); builder.build_unreachable(); } -fn cxa_allocate_exception<'a, 'ctx, 'env>( - env: &Env<'a, 'ctx, 'env>, - exception_size: IntValue<'ctx>, -) -> BasicValueEnum<'ctx> { - let name = "__cxa_allocate_exception"; - - let module = env.module; - let context = env.context; - let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); - - let function = match module.get_function(&name) { - Some(gvalue) => gvalue, - None => { - // void *__cxa_allocate_exception(size_t thrown_size); - let cxa_allocate_exception = add_func( - module, - name, - u8_ptr.fn_type(&[context.i64_type().into()], false), - Linkage::External, - C_CALL_CONV, - ); - - cxa_allocate_exception - } - }; - let call = env.builder.build_call( - function, - &[exception_size.into()], - "exception_object_void_ptr", - ); - - call.set_call_convention(C_CALL_CONV); - call.try_as_basic_value().left().unwrap() -} - -fn cxa_throw_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, info: BasicValueEnum<'ctx>) { - let name = "__cxa_throw"; - - let module = env.module; - let context = env.context; - let builder = env.builder; - - let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); - - let function = match module.get_function(&name) { - Some(value) => value, - None => { - // void __cxa_throw (void *thrown_exception, std::type_info *tinfo, void (*dest) (void *) ); - let cxa_throw = add_func( - module, - name, - context - .void_type() - .fn_type(&[u8_ptr.into(), u8_ptr.into(), u8_ptr.into()], false), - Linkage::External, - C_CALL_CONV, - ); - - cxa_throw - } - }; - - // global storing the type info of a c++ int (equivalent to `i32` in llvm) - // we just need any valid such value, and arbitrarily use this one - let ztii = match module.get_global("_ZTIi") { - Some(gvalue) => gvalue.as_pointer_value(), - None => { - let ztii = module.add_global(u8_ptr, Some(AddressSpace::Generic), "_ZTIi"); - ztii.set_linkage(Linkage::External); - - ztii.as_pointer_value() - } - }; - - let type_info = builder.build_bitcast(ztii, u8_ptr, "cast"); - let null: BasicValueEnum = u8_ptr.const_zero().into(); - - let call = builder.build_call(function, &[info, type_info, null], "throw"); - call.set_call_convention(C_CALL_CONV); -} - fn get_foreign_symbol<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, foreign_symbol: roc_module::ident::ForeignSymbol, @@ -5996,86 +6102,6 @@ fn get_foreign_symbol<'a, 'ctx, 'env>( } } -fn get_gxx_personality_v0<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> FunctionValue<'ctx> { - let name = "__gxx_personality_v0"; - - let module = env.module; - let context = env.context; - - match module.get_function(&name) { - Some(gvalue) => gvalue, - None => { - let personality_func = add_func( - module, - name, - context.i64_type().fn_type(&[], false), - Linkage::External, - C_CALL_CONV, - ); - - personality_func - } - } -} - -fn cxa_end_catch(env: &Env<'_, '_, '_>) { - let name = "__cxa_end_catch"; - - let module = env.module; - let context = env.context; - - let function = match module.get_function(&name) { - Some(gvalue) => gvalue, - None => { - let cxa_end_catch = add_func( - module, - name, - context.void_type().fn_type(&[], false), - Linkage::External, - C_CALL_CONV, - ); - - cxa_end_catch - } - }; - let call = env.builder.build_call(function, &[], "never_used"); - - call.set_call_convention(C_CALL_CONV); -} - -fn cxa_begin_catch<'a, 'ctx, 'env>( - env: &Env<'a, 'ctx, 'env>, - exception_ptr: BasicValueEnum<'ctx>, -) -> BasicValueEnum<'ctx> { - let name = "__cxa_begin_catch"; - - let module = env.module; - let context = env.context; - - let function = match module.get_function(&name) { - Some(gvalue) => gvalue, - None => { - let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); - - let cxa_begin_catch = add_func( - module, - name, - u8_ptr.fn_type(&[u8_ptr.into()], false), - Linkage::External, - C_CALL_CONV, - ); - - cxa_begin_catch - } - }; - let call = env - .builder - .build_call(function, &[exception_ptr], "exception_payload_ptr"); - - call.set_call_convention(C_CALL_CONV); - call.try_as_basic_value().left().unwrap() -} - /// Add a function to a module, after asserting that the function is unique. /// We never want to define the same function twice in the same module! /// The result can be bugs that are difficult to track down. diff --git a/compiler/gen_llvm/src/llvm/build_dict.rs b/compiler/gen_llvm/src/llvm/build_dict.rs index 73f799466e..a134c0486c 100644 --- a/compiler/gen_llvm/src/llvm/build_dict.rs +++ b/compiler/gen_llvm/src/llvm/build_dict.rs @@ -64,7 +64,7 @@ pub fn dict_len<'a, 'ctx, 'env>( .build_alloca(dict_as_zig_dict.get_type(), "dict_ptr"); env.builder.build_store(dict_ptr, dict_as_zig_dict); - call_bitcode_fn(env, &[dict_ptr.into()], &bitcode::DICT_LEN) + call_bitcode_fn(env, &[dict_ptr.into()], bitcode::DICT_LEN) } Layout::Builtin(Builtin::EmptyDict) => ctx.i64_type().const_zero().into(), _ => unreachable!("Invalid layout given to Dict.len : {:?}", dict_layout), @@ -78,7 +78,7 @@ pub fn dict_empty<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<' // we must give a pointer for the bitcode function to write the result into let result_alloc = env.builder.build_alloca(roc_dict_type, "dict_empty"); - call_void_bitcode_fn(env, &[result_alloc.into()], &bitcode::DICT_EMPTY); + call_void_bitcode_fn(env, &[result_alloc.into()], bitcode::DICT_EMPTY); env.builder.build_load(result_alloc, "load_result") } @@ -140,7 +140,7 @@ pub fn dict_insert<'a, 'ctx, 'env>( dec_value_fn.as_global_value().as_pointer_value().into(), result_ptr.into(), ], - &bitcode::DICT_INSERT, + bitcode::DICT_INSERT, ); env.builder.build_load(result_ptr, "load_result") @@ -199,7 +199,7 @@ pub fn dict_remove<'a, 'ctx, 'env>( dec_value_fn.as_global_value().as_pointer_value().into(), result_ptr.into(), ], - &bitcode::DICT_REMOVE, + bitcode::DICT_REMOVE, ); env.builder.build_load(result_ptr, "load_result") @@ -250,7 +250,7 @@ pub fn dict_contains<'a, 'ctx, 'env>( hash_fn.as_global_value().as_pointer_value().into(), eq_fn.as_global_value().as_pointer_value().into(), ], - &bitcode::DICT_CONTAINS, + bitcode::DICT_CONTAINS, ) } @@ -303,7 +303,7 @@ pub fn dict_get<'a, 'ctx, 'env>( eq_fn.as_global_value().as_pointer_value().into(), inc_value_fn.as_global_value().as_pointer_value().into(), ], - &bitcode::DICT_GET, + bitcode::DICT_GET, ) .into_struct_value(); @@ -415,7 +415,7 @@ pub fn dict_elements_rc<'a, 'ctx, 'env>( key_fn.as_global_value().as_pointer_value().into(), value_fn.as_global_value().as_pointer_value().into(), ], - &bitcode::DICT_ELEMENTS_RC, + bitcode::DICT_ELEMENTS_RC, ); } @@ -460,7 +460,7 @@ pub fn dict_keys<'a, 'ctx, 'env>( inc_key_fn.as_global_value().as_pointer_value().into(), list_ptr.into(), ], - &bitcode::DICT_KEYS, + bitcode::DICT_KEYS, ); let list_ptr = env @@ -527,7 +527,7 @@ pub fn dict_union<'a, 'ctx, 'env>( inc_value_fn.as_global_value().as_pointer_value().into(), output_ptr.into(), ], - &bitcode::DICT_UNION, + bitcode::DICT_UNION, ); env.builder.build_load(output_ptr, "load_output_ptr") @@ -549,7 +549,7 @@ pub fn dict_difference<'a, 'ctx, 'env>( dict2, key_layout, value_layout, - &bitcode::DICT_DIFFERENCE, + bitcode::DICT_DIFFERENCE, ) } @@ -569,7 +569,7 @@ pub fn dict_intersection<'a, 'ctx, 'env>( dict2, key_layout, value_layout, - &bitcode::DICT_INTERSECTION, + bitcode::DICT_INTERSECTION, ) } @@ -674,7 +674,7 @@ pub fn dict_walk<'a, 'ctx, 'env>( layout_width(env, accum_layout), env.builder.build_bitcast(output_ptr, u8_ptr, "to_opaque"), ], - &bitcode::DICT_WALK, + bitcode::DICT_WALK, ); env.builder.build_load(output_ptr, "load_output_ptr") @@ -721,7 +721,7 @@ pub fn dict_values<'a, 'ctx, 'env>( inc_value_fn.as_global_value().as_pointer_value().into(), list_ptr.into(), ], - &bitcode::DICT_VALUES, + bitcode::DICT_VALUES, ); let list_ptr = env @@ -784,7 +784,7 @@ pub fn set_from_list<'a, 'ctx, 'env>( dec_key_fn.as_global_value().as_pointer_value().into(), result_alloca.into(), ], - &bitcode::SET_FROM_LIST, + bitcode::SET_FROM_LIST, ); env.builder.build_load(result_alloca, "load_result") @@ -800,7 +800,7 @@ fn build_hash_wrapper<'a, 'ctx, 'env>( let symbol = Symbol::GENERIC_HASH_REF; let fn_name = layout_ids - .get(symbol, &layout) + .get(symbol, layout) .to_symbol_string(symbol, &env.interns); let function_value = match env.module.get_function(fn_name.as_str()) { @@ -830,8 +830,8 @@ fn build_hash_wrapper<'a, 'ctx, 'env>( let seed_arg = it.next().unwrap().into_int_value(); let value_ptr = it.next().unwrap().into_pointer_value(); - seed_arg.set_name(Symbol::ARG_1.ident_string(&env.interns)); - value_ptr.set_name(Symbol::ARG_2.ident_string(&env.interns)); + seed_arg.set_name(Symbol::ARG_1.as_str(&env.interns)); + value_ptr.set_name(Symbol::ARG_2.as_str(&env.interns)); let value_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic); @@ -867,8 +867,7 @@ fn dict_symbol_to_zig_dict<'a, 'ctx, 'env>( let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap(); - complex_bitcast(&env.builder, dict, zig_dict_type.into(), "dict_to_zig_dict") - .into_struct_value() + complex_bitcast(env.builder, dict, zig_dict_type.into(), "dict_to_zig_dict").into_struct_value() } fn zig_dict_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> inkwell::types::StructType<'ctx> { diff --git a/compiler/gen_llvm/src/llvm/build_hash.rs b/compiler/gen_llvm/src/llvm/build_hash.rs index 61a74dd232..85e82e18e8 100644 --- a/compiler/gen_llvm/src/llvm/build_hash.rs +++ b/compiler/gen_llvm/src/llvm/build_hash.rs @@ -1,5 +1,6 @@ use crate::debug_info_init; use crate::llvm::bitcode::call_bitcode_fn; +use crate::llvm::build::tag_pointer_clear_tag_id; use crate::llvm::build::Env; use crate::llvm::build::{cast_block_of_memory_to_tag, get_tag_id, FAST_CALL_CONV, TAG_DATA_INDEX}; use crate::llvm::build_str; @@ -87,10 +88,6 @@ fn build_hash_layout<'a, 'ctx, 'env>( ) } }, - - Layout::Closure(_, _, _) => { - unreachable!("the type system will guarantee these are never hashed") - } } } @@ -127,8 +124,9 @@ fn hash_builtin<'a, 'ctx, 'env>( | Builtin::Float32 | Builtin::Float128 | Builtin::Float16 + | Builtin::Decimal | Builtin::Usize => { - let hash_bytes = store_and_use_as_u8_ptr(env, val, &layout); + let hash_bytes = store_and_use_as_u8_ptr(env, val, layout); hash_bitcode_fn(env, seed, hash_bytes, layout.stack_size(ptr_bytes)) } Builtin::Str => { @@ -136,7 +134,7 @@ fn hash_builtin<'a, 'ctx, 'env>( call_bitcode_fn( env, &[seed.into(), build_str::str_to_i128(env, val).into()], - &bitcode::DICT_HASH_STR, + bitcode::DICT_HASH_STR, ) .into_int_value() } @@ -234,8 +232,8 @@ fn build_hash_struct_help<'a, 'ctx, 'env>( let seed = it.next().unwrap().into_int_value(); let value = it.next().unwrap().into_struct_value(); - seed.set_name(Symbol::ARG_1.ident_string(&env.interns)); - value.set_name(Symbol::ARG_2.ident_string(&env.interns)); + seed.set_name(Symbol::ARG_1.as_str(&env.interns)); + value.set_name(Symbol::ARG_2.as_str(&env.interns)); let entry = ctx.append_basic_block(parent, "entry"); env.builder.position_at_end(entry); @@ -325,7 +323,7 @@ fn build_hash_tag<'a, 'ctx, 'env>( let symbol = Symbol::GENERIC_HASH; let fn_name = layout_ids - .get(symbol, &layout) + .get(symbol, layout) .to_symbol_string(symbol, &env.interns); let function = match env.module.get_function(fn_name.as_str()) { @@ -333,7 +331,7 @@ fn build_hash_tag<'a, 'ctx, 'env>( None => { let seed_type = env.context.i64_type(); - let arg_type = basic_type_from_layout(env, &layout); + let arg_type = basic_type_from_layout(env, layout); let function_value = crate::llvm::refcounting::build_header_help( env, @@ -375,8 +373,8 @@ fn build_hash_tag_help<'a, 'ctx, 'env>( let seed = it.next().unwrap().into_int_value(); let value = it.next().unwrap(); - seed.set_name(Symbol::ARG_1.ident_string(&env.interns)); - value.set_name(Symbol::ARG_2.ident_string(&env.interns)); + seed.set_name(Symbol::ARG_1.as_str(&env.interns)); + value.set_name(Symbol::ARG_2.as_str(&env.interns)); let entry = ctx.append_basic_block(parent, "entry"); env.builder.position_at_end(entry); @@ -493,14 +491,9 @@ fn hash_tag<'a, 'ctx, 'env>( ); // hash the tag data - let answer = hash_ptr_to_struct( - env, - layout_ids, - union_layout, - field_layouts, - seed, - tag.into_pointer_value(), - ); + let tag = tag_pointer_clear_tag_id(env, tag.into_pointer_value()); + let answer = + hash_ptr_to_struct(env, layout_ids, union_layout, field_layouts, seed, tag); merge_phi.add_incoming(&[(&answer, block)]); env.builder.build_unconditional_branch(merge_block); @@ -598,6 +591,7 @@ fn hash_tag<'a, 'ctx, 'env>( ); // hash tag data + let tag = tag_pointer_clear_tag_id(env, tag); let answer = hash_ptr_to_struct( env, layout_ids, @@ -661,7 +655,7 @@ fn build_hash_list<'a, 'ctx, 'env>( let symbol = Symbol::GENERIC_HASH; let fn_name = layout_ids - .get(symbol, &layout) + .get(symbol, layout) .to_symbol_string(symbol, &env.interns); let function = match env.module.get_function(fn_name.as_str()) { @@ -669,7 +663,7 @@ fn build_hash_list<'a, 'ctx, 'env>( None => { let seed_type = env.context.i64_type(); - let arg_type = basic_type_from_layout(env, &layout); + let arg_type = basic_type_from_layout(env, layout); let function_value = crate::llvm::refcounting::build_header_help( env, @@ -718,8 +712,8 @@ fn build_hash_list_help<'a, 'ctx, 'env>( let seed = it.next().unwrap().into_int_value(); let value = it.next().unwrap().into_struct_value(); - seed.set_name(Symbol::ARG_1.ident_string(&env.interns)); - value.set_name(Symbol::ARG_2.ident_string(&env.interns)); + seed.set_name(Symbol::ARG_1.as_str(&env.interns)); + value.set_name(Symbol::ARG_2.as_str(&env.interns)); let entry = ctx.append_basic_block(parent, "entry"); env.builder.position_at_end(entry); @@ -872,7 +866,7 @@ fn store_and_use_as_u8_ptr<'a, 'ctx, 'env>( value: BasicValueEnum<'ctx>, layout: &Layout<'a>, ) -> PointerValue<'ctx> { - let basic_type = basic_type_from_layout(env, &layout); + let basic_type = basic_type_from_layout(env, layout); let alloc = env.builder.build_alloca(basic_type, "store"); env.builder.build_store(alloc, value); @@ -897,7 +891,7 @@ fn hash_bitcode_fn<'a, 'ctx, 'env>( call_bitcode_fn( env, &[seed.into(), buffer.into(), num_bytes.into()], - &bitcode::DICT_HASH, + bitcode::DICT_HASH, ) .into_int_value() } diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index 7aa7e07ead..bd0d6344cd 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -94,7 +94,7 @@ pub fn list_single<'a, 'ctx, 'env>( pass_element_as_opaque(env, element), layout_width(env, element_layout), ], - &bitcode::LIST_SINGLE, + bitcode::LIST_SINGLE, ) } @@ -121,70 +121,6 @@ pub fn list_repeat<'a, 'ctx, 'env>( ) } -/// List.prepend : List elem, elem -> List elem -pub fn list_prepend<'a, 'ctx, 'env>( - env: &Env<'a, 'ctx, 'env>, - original_wrapper: StructValue<'ctx>, - elem: BasicValueEnum<'ctx>, - elem_layout: &Layout<'a>, -) -> BasicValueEnum<'ctx> { - let builder = env.builder; - let ctx = env.context; - - // Load the usize length from the wrapper. - let len = list_len(builder, original_wrapper); - let elem_type = basic_type_from_layout(env, elem_layout); - let ptr_type = elem_type.ptr_type(AddressSpace::Generic); - let list_ptr = load_list_ptr(builder, original_wrapper, ptr_type); - - // The output list length, which is the old list length + 1 - let new_list_len = env.builder.build_int_add( - ctx.i64_type().const_int(1_u64, false), - len, - "new_list_length", - ); - - // Allocate space for the new array that we'll copy into. - let clone_ptr = allocate_list(env, elem_layout, new_list_len); - - builder.build_store(clone_ptr, elem); - - let index_1_ptr = unsafe { - builder.build_in_bounds_gep( - clone_ptr, - &[ctx.i64_type().const_int(1_u64, false)], - "load_index", - ) - }; - - // Calculate the number of bytes we'll need to allocate. - let elem_bytes = env - .ptr_int() - .const_int(elem_layout.stack_size(env.ptr_bytes) as u64, false); - - // This is the size of the list coming in, before we have added an element - // to the beginning. - let list_size = env - .builder - .build_int_mul(elem_bytes, len, "mul_old_len_by_elem_bytes"); - - let ptr_bytes = env.ptr_bytes; - - if elem_layout.safe_to_memcpy() { - // Copy the bytes from the original array into the new - // one we just allocated - // - // TODO how do we decide when to do the small memcpy vs the normal one? - builder - .build_memcpy(index_1_ptr, ptr_bytes, list_ptr, ptr_bytes, list_size) - .unwrap(); - } else { - panic!("TODO Cranelift currently only knows how to clone list elements that are Copy."); - } - - store_list(env, clone_ptr, new_list_len) -} - /// List.join : List (List elem) -> List elem pub fn list_join<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, @@ -207,7 +143,7 @@ pub fn list_join<'a, 'ctx, 'env>( env.alignment_intvalue(element_layout), layout_width(env, element_layout), ], - &bitcode::LIST_JOIN, + bitcode::LIST_JOIN, ) } _ => { @@ -240,7 +176,7 @@ pub fn list_reverse<'a, 'ctx, 'env>( env.alignment_intvalue(&element_layout), layout_width(env, &element_layout), ], - &bitcode::LIST_REVERSE, + bitcode::LIST_REVERSE, ) } @@ -292,11 +228,30 @@ pub fn list_append<'a, 'ctx, 'env>( env, &[ pass_list_as_i128(env, original_wrapper.into()), - env.alignment_intvalue(&element_layout), + env.alignment_intvalue(element_layout), pass_element_as_opaque(env, element), layout_width(env, element_layout), ], - &bitcode::LIST_APPEND, + bitcode::LIST_APPEND, + ) +} + +/// List.prepend : List elem, elem -> List elem +pub fn list_prepend<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + original_wrapper: StructValue<'ctx>, + element: BasicValueEnum<'ctx>, + element_layout: &Layout<'a>, +) -> BasicValueEnum<'ctx> { + call_bitcode_fn_returns_list( + env, + &[ + pass_list_as_i128(env, original_wrapper.into()), + env.alignment_intvalue(element_layout), + pass_element_as_opaque(env, element), + layout_width(env, element_layout), + ], + bitcode::LIST_PREPEND, ) } @@ -312,12 +267,12 @@ pub fn list_swap<'a, 'ctx, 'env>( env, &[ pass_list_as_i128(env, original_wrapper.into()), - env.alignment_intvalue(&element_layout), - layout_width(env, &element_layout), + env.alignment_intvalue(element_layout), + layout_width(env, element_layout), index_1.into(), index_2.into(), ], - &bitcode::LIST_SWAP, + bitcode::LIST_SWAP, ) } @@ -329,17 +284,17 @@ pub fn list_drop<'a, 'ctx, 'env>( count: IntValue<'ctx>, element_layout: &Layout<'a>, ) -> BasicValueEnum<'ctx> { - let dec_element_fn = build_dec_wrapper(env, layout_ids, &element_layout); + let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout); call_bitcode_fn_returns_list( env, &[ pass_list_as_i128(env, original_wrapper.into()), - env.alignment_intvalue(&element_layout), - layout_width(env, &element_layout), + env.alignment_intvalue(element_layout), + layout_width(env, element_layout), count.into(), dec_element_fn.as_global_value().as_pointer_value().into(), ], - &bitcode::LIST_DROP, + bitcode::LIST_DROP, ) } @@ -378,7 +333,7 @@ pub fn list_set<'a, 'ctx, 'env>( &[ bytes.into(), length.into(), - env.alignment_intvalue(&element_layout), + env.alignment_intvalue(element_layout), index.into(), pass_element_as_opaque(env, element), layout_width(env, element_layout), @@ -457,7 +412,7 @@ pub fn list_walk_generic<'a, 'ctx, 'env>( roc_function_call.inc_n_data.into(), roc_function_call.data_is_owned.into(), pass_as_opaque(env, default_ptr), - env.alignment_intvalue(&element_layout), + env.alignment_intvalue(element_layout), layout_width(env, element_layout), layout_width(env, default_layout), pass_as_opaque(env, result_ptr), @@ -488,7 +443,7 @@ pub fn list_walk_generic<'a, 'ctx, 'env>( roc_function_call.inc_n_data.into(), roc_function_call.data_is_owned.into(), pass_as_opaque(env, default_ptr), - env.alignment_intvalue(&element_layout), + env.alignment_intvalue(element_layout), layout_width(env, element_layout), layout_width(env, function_call_return_layout), layout_width(env, default_layout), @@ -564,7 +519,7 @@ pub fn list_range<'a, 'ctx, 'env>( pass_as_opaque(env, low_ptr), pass_as_opaque(env, high_ptr), ], - &bitcode::LIST_RANGE, + bitcode::LIST_RANGE, ) } @@ -612,12 +567,12 @@ pub fn list_keep_if<'a, 'ctx, 'env>( 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(&element_layout), + env.alignment_intvalue(element_layout), layout_width(env, element_layout), inc_element_fn.as_global_value().as_pointer_value().into(), dec_element_fn.as_global_value().as_pointer_value().into(), ], - &bitcode::LIST_KEEP_IF, + bitcode::LIST_KEEP_IF, ) } @@ -654,7 +609,7 @@ pub fn list_keep_oks<'a, 'ctx, 'env>( 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(&before_layout), + env.alignment_intvalue(before_layout), layout_width(env, before_layout), layout_width(env, result_layout), layout_width(env, after_layout), @@ -698,7 +653,7 @@ pub fn list_keep_errs<'a, 'ctx, 'env>( 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(&before_layout), + env.alignment_intvalue(before_layout), layout_width(env, before_layout), layout_width(env, result_layout), layout_width(env, after_layout), @@ -725,7 +680,7 @@ pub fn list_sort_with<'a, 'ctx, 'env>( 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(&element_layout), + env.alignment_intvalue(element_layout), layout_width(env, element_layout), ], bitcode::LIST_SORT_WITH, @@ -748,7 +703,7 @@ pub fn list_map_with_index<'a, 'ctx, 'env>( 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(&element_layout), + env.alignment_intvalue(element_layout), layout_width(env, element_layout), layout_width(env, return_layout), ], @@ -772,7 +727,7 @@ pub fn list_map<'a, 'ctx, 'env>( 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(&element_layout), + env.alignment_intvalue(element_layout), layout_width(env, element_layout), layout_width(env, return_layout), ], @@ -874,7 +829,7 @@ pub fn list_concat<'a, 'ctx, 'env>( env.alignment_intvalue(elem_layout), layout_width(env, elem_layout), ], - &bitcode::LIST_CONCAT, + bitcode::LIST_CONCAT, ), _ => { unreachable!("Invalid List layout for List.concat {:?}", list_layout); diff --git a/compiler/gen_llvm/src/llvm/build_str.rs b/compiler/gen_llvm/src/llvm/build_str.rs index ee8576ceb8..9d3c482f9d 100644 --- a/compiler/gen_llvm/src/llvm/build_str.rs +++ b/compiler/gen_llvm/src/llvm/build_str.rs @@ -27,7 +27,7 @@ pub fn str_split<'a, 'ctx, 'env>( let segment_count = call_bitcode_fn( env, &[str_i128.into(), delim_i128.into()], - &bitcode::STR_COUNT_SEGMENTS, + bitcode::STR_COUNT_SEGMENTS, ) .into_int_value(); @@ -47,7 +47,7 @@ pub fn str_split<'a, 'ctx, 'env>( call_void_bitcode_fn( env, &[ret_list_ptr_zig_rocstr, str_i128.into(), delim_i128.into()], - &bitcode::STR_STR_SPLIT_IN_PLACE, + bitcode::STR_STR_SPLIT_IN_PLACE, ); store_list(env, ret_list_ptr, segment_count) @@ -62,7 +62,7 @@ fn str_symbol_to_i128<'a, 'ctx, 'env>( let i128_type = env.context.i128_type().into(); - complex_bitcast(&env.builder, string, i128_type, "str_to_i128").into_int_value() + complex_bitcast(env.builder, string, i128_type, "str_to_i128").into_int_value() } pub fn str_to_i128<'a, 'ctx, 'env>( @@ -119,7 +119,7 @@ pub fn str_concat<'a, 'ctx, 'env>( call_bitcode_fn( env, &[str1_i128.into(), str2_i128.into()], - &bitcode::STR_CONCAT, + bitcode::STR_CONCAT, ) } @@ -138,7 +138,7 @@ pub fn str_join_with<'a, 'ctx, 'env>( call_bitcode_fn( env, &[list_i128.into(), str_i128.into()], - &bitcode::STR_JOIN_WITH, + bitcode::STR_JOIN_WITH, ) } @@ -151,7 +151,7 @@ pub fn str_number_of_bytes<'a, 'ctx, 'env>( // the builtin will always return an u64 let length = - call_bitcode_fn(env, &[str_i128.into()], &bitcode::STR_NUMBER_OF_BYTES).into_int_value(); + call_bitcode_fn(env, &[str_i128.into()], bitcode::STR_NUMBER_OF_BYTES).into_int_value(); // cast to the appropriate usize of the current build env.builder @@ -171,11 +171,11 @@ pub fn str_starts_with<'a, 'ctx, 'env>( call_bitcode_fn( env, &[str_i128.into(), prefix_i128.into()], - &bitcode::STR_STARTS_WITH, + bitcode::STR_STARTS_WITH, ) } -/// Str.startsWithCodePoint : Str, U32 -> Bool +/// Str.startsWithCodePt : Str, U32 -> Bool pub fn str_starts_with_code_point<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, scope: &Scope<'a, 'ctx>, @@ -188,7 +188,7 @@ pub fn str_starts_with_code_point<'a, 'ctx, 'env>( call_bitcode_fn( env, &[str_i128.into(), prefix], - &bitcode::STR_STARTS_WITH_CODE_POINT, + bitcode::STR_STARTS_WITH_CODE_PT, ) } @@ -205,7 +205,7 @@ pub fn str_ends_with<'a, 'ctx, 'env>( call_bitcode_fn( env, &[str_i128.into(), prefix_i128.into()], - &bitcode::STR_ENDS_WITH, + bitcode::STR_ENDS_WITH, ) } @@ -220,7 +220,7 @@ pub fn str_count_graphemes<'a, 'ctx, 'env>( call_bitcode_fn( env, &[str_i128.into()], - &bitcode::STR_COUNT_GRAPEHEME_CLUSTERS, + bitcode::STR_COUNT_GRAPEHEME_CLUSTERS, ) } @@ -232,11 +232,11 @@ pub fn str_from_int<'a, 'ctx, 'env>( ) -> BasicValueEnum<'ctx> { let int = load_symbol(scope, &int_symbol); - call_bitcode_fn(env, &[int], &bitcode::STR_FROM_INT) + call_bitcode_fn(env, &[int], bitcode::STR_FROM_INT) } -/// Str.toBytes : Str -> List U8 -pub fn str_to_bytes<'a, 'ctx, 'env>( +/// Str.toUtf8 : Str -> List U8 +pub fn str_to_utf8<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, original_wrapper: StructValue<'ctx>, ) -> BasicValueEnum<'ctx> { @@ -244,10 +244,66 @@ pub fn str_to_bytes<'a, 'ctx, 'env>( env.builder, original_wrapper.into(), env.context.i128_type().into(), - "to_bytes", + "to_utf8", ); - call_bitcode_fn_returns_list(env, &[string], &bitcode::STR_TO_BYTES) + call_bitcode_fn_returns_list(env, &[string], bitcode::STR_TO_UTF8) +} + +/// Str.fromUtf8 : List U8, { count : Nat, start : Nat } -> { a : Bool, b : Str, c : Nat, d : I8 } +pub fn str_from_utf8_range<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + _parent: FunctionValue<'ctx>, + list_wrapper: StructValue<'ctx>, + count_and_start: StructValue<'ctx>, +) -> BasicValueEnum<'ctx> { + let builder = env.builder; + let ctx = env.context; + + let result_type = env.module.get_struct_type("str.FromUtf8Result").unwrap(); + let result_ptr = builder.build_alloca(result_type, "alloca_utf8_validate_bytes_result"); + + call_void_bitcode_fn( + env, + &[ + complex_bitcast( + env.builder, + list_wrapper.into(), + env.context.i128_type().into(), + "to_i128", + ), + // TODO: This won't work for 32 bit targets! + complex_bitcast( + env.builder, + count_and_start.into(), + env.context.i128_type().into(), + "to_i128", + ), + result_ptr.into(), + ], + bitcode::STR_FROM_UTF8_RANGE, + ); + + let record_type = env.context.struct_type( + &[ + env.ptr_int().into(), + super::convert::zig_str_type(env).into(), + env.context.bool_type().into(), + ctx.i8_type().into(), + ], + false, + ); + + let result_ptr_cast = env + .builder + .build_bitcast( + result_ptr, + record_type.ptr_type(AddressSpace::Generic), + "to_unnamed", + ) + .into_pointer_value(); + + builder.build_load(result_ptr_cast, "load_utf8_validate_bytes_result") } /// Str.fromUtf8 : List U8 -> { a : Bool, b : Str, c : Nat, d : I8 } @@ -273,7 +329,7 @@ pub fn str_from_utf8<'a, 'ctx, 'env>( ), result_ptr.into(), ], - &bitcode::STR_FROM_UTF8, + bitcode::STR_FROM_UTF8, ); let record_type = env.context.struct_type( @@ -306,7 +362,7 @@ pub fn str_from_float<'a, 'ctx, 'env>( ) -> BasicValueEnum<'ctx> { let float = load_symbol(scope, &int_symbol); - call_bitcode_fn(env, &[float], &bitcode::STR_FROM_FLOAT) + call_bitcode_fn(env, &[float], bitcode::STR_FROM_FLOAT) } /// Str.equal : Str, Str -> Bool @@ -321,7 +377,7 @@ pub fn str_equal<'a, 'ctx, 'env>( call_bitcode_fn( env, &[str1_i128.into(), str2_i128.into()], - &bitcode::STR_EQUAL, + bitcode::STR_EQUAL, ) } diff --git a/compiler/gen_llvm/src/llvm/compare.rs b/compiler/gen_llvm/src/llvm/compare.rs index 2a9d1d3fa3..10463094df 100644 --- a/compiler/gen_llvm/src/llvm/compare.rs +++ b/compiler/gen_llvm/src/llvm/compare.rs @@ -1,5 +1,7 @@ -use crate::llvm::build::Env; -use crate::llvm::build::{cast_block_of_memory_to_tag, get_tag_id, FAST_CALL_CONV}; +use crate::llvm::bitcode::call_bitcode_fn; +use crate::llvm::build::{ + cast_block_of_memory_to_tag, get_tag_id, tag_pointer_clear_tag_id, Env, FAST_CALL_CONV, +}; use crate::llvm::build_list::{list_len, load_list_ptr}; use crate::llvm::build_str::str_equal; use crate::llvm::convert::basic_type_from_layout; @@ -9,6 +11,7 @@ use inkwell::values::{ BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue, }; use inkwell::{AddressSpace, FloatPredicate, IntPredicate}; +use roc_builtins::bitcode; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout}; @@ -96,6 +99,7 @@ fn build_eq_builtin<'a, 'ctx, 'env>( Builtin::Usize => int_cmp(IntPredicate::EQ, "eq_usize"), + Builtin::Decimal => call_bitcode_fn(env, &[lhs_val, rhs_val], bitcode::DEC_EQ), Builtin::Float128 => float_cmp(FloatPredicate::OEQ, "eq_f128"), Builtin::Float64 => float_cmp(FloatPredicate::OEQ, "eq_f64"), Builtin::Float32 => float_cmp(FloatPredicate::OEQ, "eq_f32"), @@ -194,10 +198,6 @@ fn build_eq<'a, 'ctx, 'env>( ) } }, - - Layout::Closure(_, _, _) => { - unreachable!("the type system will guarantee these are never compared") - } } } @@ -241,6 +241,7 @@ fn build_neq_builtin<'a, 'ctx, 'env>( Builtin::Usize => int_cmp(IntPredicate::NE, "neq_usize"), + Builtin::Decimal => call_bitcode_fn(env, &[lhs_val, rhs_val], bitcode::DEC_NEQ), Builtin::Float128 => float_cmp(FloatPredicate::ONE, "neq_f128"), Builtin::Float64 => float_cmp(FloatPredicate::ONE, "neq_f64"), Builtin::Float32 => float_cmp(FloatPredicate::ONE, "neq_f32"), @@ -335,10 +336,6 @@ fn build_neq<'a, 'ctx, 'env>( Layout::RecursivePointer => { unreachable!("recursion pointers should never be compared directly") } - - Layout::Closure(_, _, _) => { - unreachable!("the type system will guarantee these are never compared") - } } } @@ -356,13 +353,13 @@ fn build_list_eq<'a, 'ctx, 'env>( let symbol = Symbol::LIST_EQ; let fn_name = layout_ids - .get(symbol, &element_layout) + .get(symbol, element_layout) .to_symbol_string(symbol, &env.interns); let function = match env.module.get_function(fn_name.as_str()) { Some(function_value) => function_value, None => { - let arg_type = basic_type_from_layout(env, &list_layout); + let arg_type = basic_type_from_layout(env, list_layout); let function_value = crate::llvm::refcounting::build_header_help( env, @@ -423,7 +420,7 @@ fn build_list_eq_help<'a, 'ctx, 'env>( /* current_scope */ lexical_block.as_debug_info_scope(), /* inlined_at */ None, ); - builder.set_current_debug_location(&ctx, loc); + builder.set_current_debug_location(ctx, loc); } // Add args to scope @@ -431,8 +428,8 @@ fn build_list_eq_help<'a, 'ctx, 'env>( let list1 = it.next().unwrap().into_struct_value(); let list2 = it.next().unwrap().into_struct_value(); - list1.set_name(Symbol::ARG_1.ident_string(&env.interns)); - list2.set_name(Symbol::ARG_2.ident_string(&env.interns)); + list1.set_name(Symbol::ARG_1.as_str(&env.interns)); + list2.set_name(Symbol::ARG_2.as_str(&env.interns)); let entry = ctx.append_basic_block(parent, "entry"); env.builder.position_at_end(entry); @@ -631,7 +628,7 @@ fn build_struct_eq_help<'a, 'ctx, 'env>( /* current_scope */ lexical_block.as_debug_info_scope(), /* inlined_at */ None, ); - builder.set_current_debug_location(&ctx, loc); + builder.set_current_debug_location(ctx, loc); } // Add args to scope @@ -639,8 +636,8 @@ fn build_struct_eq_help<'a, 'ctx, 'env>( let struct1 = it.next().unwrap().into_struct_value(); let struct2 = it.next().unwrap().into_struct_value(); - struct1.set_name(Symbol::ARG_1.ident_string(&env.interns)); - struct2.set_name(Symbol::ARG_2.ident_string(&env.interns)); + struct1.set_name(Symbol::ARG_1.as_str(&env.interns)); + struct2.set_name(Symbol::ARG_2.as_str(&env.interns)); let entry = ctx.append_basic_block(parent, "entry"); let start = ctx.append_basic_block(parent, "start"); @@ -747,13 +744,13 @@ fn build_tag_eq<'a, 'ctx, 'env>( let symbol = Symbol::GENERIC_EQ; let fn_name = layout_ids - .get(symbol, &tag_layout) + .get(symbol, tag_layout) .to_symbol_string(symbol, &env.interns); let function = match env.module.get_function(fn_name.as_str()) { Some(function_value) => function_value, None => { - let arg_type = basic_type_from_layout(env, &tag_layout); + let arg_type = basic_type_from_layout(env, tag_layout); let function_value = crate::llvm::refcounting::build_header_help( env, @@ -812,7 +809,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>( /* current_scope */ lexical_block.as_debug_info_scope(), /* inlined_at */ None, ); - builder.set_current_debug_location(&ctx, loc); + builder.set_current_debug_location(ctx, loc); } // Add args to scope @@ -820,8 +817,8 @@ fn build_tag_eq_help<'a, 'ctx, 'env>( let tag1 = it.next().unwrap(); let tag2 = it.next().unwrap(); - tag1.set_name(Symbol::ARG_1.ident_string(&env.interns)); - tag2.set_name(Symbol::ARG_2.ident_string(&env.interns)); + tag1.set_name(Symbol::ARG_1.as_str(&env.interns)); + tag2.set_name(Symbol::ARG_2.as_str(&env.interns)); let entry = ctx.append_basic_block(parent, "entry"); @@ -925,6 +922,10 @@ fn build_tag_eq_help<'a, 'ctx, 'env>( let id1 = get_tag_id(env, parent, union_layout, tag1); let id2 = get_tag_id(env, parent, union_layout, tag2); + // clear the tag_id so we get a pointer to the actual data + let tag1 = tag_pointer_clear_tag_id(env, tag1.into_pointer_value()); + let tag2 = tag_pointer_clear_tag_id(env, tag2.into_pointer_value()); + let compare_tag_fields = ctx.append_basic_block(parent, "compare_tag_fields"); let same_tag = @@ -944,14 +945,8 @@ fn build_tag_eq_help<'a, 'ctx, 'env>( let block = env.context.append_basic_block(parent, "tag_id_modify"); env.builder.position_at_end(block); - let answer = eq_ptr_to_struct( - env, - layout_ids, - union_layout, - field_layouts, - tag1.into_pointer_value(), - tag2.into_pointer_value(), - ); + let answer = + eq_ptr_to_struct(env, layout_ids, union_layout, field_layouts, tag1, tag2); env.builder.build_return(Some(&answer)); @@ -1073,6 +1068,10 @@ fn build_tag_eq_help<'a, 'ctx, 'env>( let id1 = get_tag_id(env, parent, union_layout, tag1); let id2 = get_tag_id(env, parent, union_layout, tag2); + // clear the tag_id so we get a pointer to the actual data + let tag1 = tag_pointer_clear_tag_id(env, tag1.into_pointer_value()); + let tag2 = tag_pointer_clear_tag_id(env, tag2.into_pointer_value()); + let compare_tag_fields = ctx.append_basic_block(parent, "compare_tag_fields"); let same_tag = @@ -1093,14 +1092,8 @@ fn build_tag_eq_help<'a, 'ctx, 'env>( let block = env.context.append_basic_block(parent, "tag_id_modify"); env.builder.position_at_end(block); - let answer = eq_ptr_to_struct( - env, - layout_ids, - union_layout, - field_layouts, - tag1.into_pointer_value(), - tag2.into_pointer_value(), - ); + let answer = + eq_ptr_to_struct(env, layout_ids, union_layout, field_layouts, tag1, tag2); env.builder.build_return(Some(&answer)); diff --git a/compiler/gen_llvm/src/llvm/convert.rs b/compiler/gen_llvm/src/llvm/convert.rs index 1cb299c0ae..1522c21e9e 100644 --- a/compiler/gen_llvm/src/llvm/convert.rs +++ b/compiler/gen_llvm/src/llvm/convert.rs @@ -26,49 +26,41 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>( use Layout::*; match layout { - Closure(_args, closure_layout, _ret_layout) => { - let closure_data_layout = closure_layout.runtime_representation(); - basic_type_from_layout(env, &closure_data_layout) - } Struct(sorted_fields) => basic_type_from_record(env, sorted_fields), - Union(variant) => { + Union(union_layout) => { use UnionLayout::*; - let tag_id_type = basic_type_from_layout(env, &variant.tag_id_layout()); + let tag_id_type = basic_type_from_layout(env, &union_layout.tag_id_layout()); - match variant { - NullableWrapped { + match union_layout { + NonRecursive(tags) => { + let data = block_of_memory_slices(env.context, tags, env.ptr_bytes); + + env.context.struct_type(&[data, tag_id_type], false).into() + } + Recursive(tags) + | NullableWrapped { other_tags: tags, .. } => { let data = block_of_memory_slices(env.context, tags, env.ptr_bytes); - env.context - .struct_type(&[data, tag_id_type], false) - .ptr_type(AddressSpace::Generic) - .into() + if union_layout.stores_tag_id_as_data(env.ptr_bytes) { + env.context + .struct_type(&[data, tag_id_type], false) + .ptr_type(AddressSpace::Generic) + .into() + } else { + data.ptr_type(AddressSpace::Generic).into() + } } NullableUnwrapped { other_fields, .. } => { - let block = - block_of_memory_slices(env.context, &[&other_fields], env.ptr_bytes); + let block = block_of_memory_slices(env.context, &[other_fields], env.ptr_bytes); block.ptr_type(AddressSpace::Generic).into() } NonNullableUnwrapped(fields) => { let block = block_of_memory_slices(env.context, &[fields], env.ptr_bytes); block.ptr_type(AddressSpace::Generic).into() } - Recursive(tags) => { - let data = block_of_memory_slices(env.context, tags, env.ptr_bytes); - - env.context - .struct_type(&[data, tag_id_type], false) - .ptr_type(AddressSpace::Generic) - .into() - } - NonRecursive(tags) => { - let data = block_of_memory_slices(env.context, tags, env.ptr_bytes); - - env.context.struct_type(&[data, tag_id_type], false).into() - } } } RecursivePointer => { @@ -100,6 +92,7 @@ pub fn basic_type_from_builtin<'a, 'ctx, 'env>( Int8 => context.i8_type().as_basic_type_enum(), Int1 => context.bool_type().as_basic_type_enum(), Usize => ptr_int(context, ptr_bytes).as_basic_type_enum(), + Decimal => context.i128_type().as_basic_type_enum(), Float128 => context.f128_type().as_basic_type_enum(), Float64 => context.f64_type().as_basic_type_enum(), Float32 => context.f32_type().as_basic_type_enum(), @@ -145,16 +138,6 @@ pub fn union_data_is_struct_type<'ctx>( context.struct_type(&[struct_type.into(), tag_id_type.into()], false) } -pub fn union_data_block_of_memory<'ctx>( - context: &'ctx Context, - tag_id_int_type: IntType<'ctx>, - layouts: &[&[Layout<'_>]], - ptr_bytes: u32, -) -> StructType<'ctx> { - let data_type = block_of_memory_slices(context, layouts, ptr_bytes); - context.struct_type(&[data_type, tag_id_int_type.into()], false) -} - pub fn block_of_memory<'ctx>( context: &'ctx Context, layout: &Layout<'_>, @@ -234,3 +217,11 @@ pub fn zig_has_tag_id_type<'a, 'ctx, 'env>( ) -> StructType<'ctx> { env.module.get_struct_type("list.HasTagId").unwrap() } + +pub fn zig_with_overflow_roc_dec<'a, 'ctx, 'env>( + env: &crate::llvm::build::Env<'a, 'ctx, 'env>, +) -> StructType<'ctx> { + env.module + .get_struct_type("utils.WithOverflow(dec.RocDec)") + .unwrap() +} diff --git a/compiler/gen_llvm/src/llvm/externs.rs b/compiler/gen_llvm/src/llvm/externs.rs index 48bea9283b..a0040102ab 100644 --- a/compiler/gen_llvm/src/llvm/externs.rs +++ b/compiler/gen_llvm/src/llvm/externs.rs @@ -1,19 +1,18 @@ +use crate::llvm::build::Env; use crate::llvm::build::{add_func, C_CALL_CONV}; use crate::llvm::convert::ptr_int; -use inkwell::builder::Builder; -use inkwell::context::Context; -use inkwell::module::{Linkage, Module}; +use inkwell::module::Linkage; use inkwell::values::BasicValue; use inkwell::AddressSpace; /// Define functions for roc_alloc, roc_realloc, and roc_dealloc /// which use libc implementations (malloc, realloc, and free) -pub fn add_default_roc_externs<'ctx>( - ctx: &'ctx Context, - module: &Module<'ctx>, - builder: &Builder<'ctx>, - ptr_bytes: u32, -) { +pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) { + let ctx = env.context; + let module = env.module; + let builder = env.builder; + let ptr_bytes = env.ptr_bytes; + let usize_type = ptr_int(ctx, ptr_bytes); let i8_ptr_type = ctx.i8_type().ptr_type(AddressSpace::Generic); @@ -139,4 +138,69 @@ pub fn add_default_roc_externs<'ctx>( crate::llvm::build::verify_fn(fn_val); } } + + add_sjlj_roc_panic(env) +} + +pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) { + let ctx = env.context; + let module = env.module; + let builder = env.builder; + + // roc_panic + { + use crate::llvm::build::LLVM_LONGJMP; + + // The type of this function (but not the implementation) should have + // already been defined by the builtins, which rely on it. + let fn_val = module.get_function("roc_panic").unwrap(); + let mut params = fn_val.get_param_iter(); + let ptr_arg = params.next().unwrap(); + + // in debug mode, this is assumed to be NullTerminatedString + let _tag_id_arg = params.next().unwrap(); + + debug_assert!(params.next().is_none()); + + let subprogram = env.new_subprogram("roc_panic"); + fn_val.set_subprogram(subprogram); + + env.dibuilder.finalize(); + + // Add a basic block for the entry point + let entry = ctx.append_basic_block(fn_val, "entry"); + + builder.position_at_end(entry); + + let buffer = crate::llvm::build::get_sjlj_buffer(env); + + // write our error message pointer + let index = env.ptr_int().const_int(3 * env.ptr_bytes as u64, false); + let message_buffer_raw = + unsafe { builder.build_gep(buffer, &[index], "raw_msg_buffer_ptr") }; + let message_buffer = builder.build_bitcast( + message_buffer_raw, + env.context + .i8_type() + .ptr_type(AddressSpace::Generic) + .ptr_type(AddressSpace::Generic), + "to **u8", + ); + + env.builder + .build_store(message_buffer.into_pointer_value(), ptr_arg); + + let tag = env.context.i32_type().const_int(1, false); + if true { + let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[buffer.into()]); + } else { + let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[buffer.into(), tag.into()]); + } + + builder.build_unreachable(); + + if cfg!(debug_assertions) { + crate::llvm::build::verify_fn(fn_val); + } + } } diff --git a/compiler/gen_llvm/src/llvm/refcounting.rs b/compiler/gen_llvm/src/llvm/refcounting.rs index 4dd072f622..2b8a0a7e67 100644 --- a/compiler/gen_llvm/src/llvm/refcounting.rs +++ b/compiler/gen_llvm/src/llvm/refcounting.rs @@ -1,12 +1,10 @@ use crate::debug_info_init; use crate::llvm::build::{ add_func, cast_basic_basic, cast_block_of_memory_to_tag, get_tag_id, get_tag_id_non_recursive, - Env, FAST_CALL_CONV, LLVM_SADD_WITH_OVERFLOW_I64, TAG_DATA_INDEX, + tag_pointer_clear_tag_id, Env, FAST_CALL_CONV, LLVM_SADD_WITH_OVERFLOW_I64, TAG_DATA_INDEX, }; use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list}; -use crate::llvm::convert::{ - basic_type_from_layout, block_of_memory_slices, ptr_int, union_data_block_of_memory, -}; +use crate::llvm::convert::{basic_type_from_layout, ptr_int}; use bumpalo::collections::Vec; use inkwell::basic_block::BasicBlock; use inkwell::context::Context; @@ -280,7 +278,7 @@ impl<'ctx> PointerToRefcount<'ctx> { // build then block { builder.position_at_end(then_block); - if !env.leak { + if !env.is_gen_test { let ptr = builder.build_pointer_cast( refcount_ptr.value, ctx.i8_type().ptr_type(AddressSpace::Generic), @@ -401,7 +399,7 @@ fn modify_refcount_struct_help<'a, 'ctx, 'env>( let arg_symbol = Symbol::ARG_1; let arg_val = fn_val.get_param_iter().next().unwrap(); - arg_val.set_name(arg_symbol.ident_string(&env.interns)); + arg_val.set_name(arg_symbol.as_str(&env.interns)); let parent = fn_val; @@ -644,87 +642,21 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>( Union(variant) => { use UnionLayout::*; - match variant { - NullableWrapped { - other_tags: tags, .. - } => { - let function = build_rec_union( - env, - layout_ids, - mode, - &WhenRecursive::Loop(*variant), - *variant, - tags, - true, - ); + if let NonRecursive(tags) = variant { + let function = modify_refcount_union(env, layout_ids, mode, when_recursive, tags); - Some(function) - } - - NullableUnwrapped { other_fields, .. } => { - let function = build_rec_union( - env, - layout_ids, - mode, - &WhenRecursive::Loop(*variant), - *variant, - env.arena.alloc([*other_fields]), - true, - ); - - Some(function) - } - - NonNullableUnwrapped(fields) => { - let function = build_rec_union( - env, - layout_ids, - mode, - &WhenRecursive::Loop(*variant), - *variant, - &*env.arena.alloc([*fields]), - true, - ); - Some(function) - } - - Recursive(tags) => { - let function = build_rec_union( - env, - layout_ids, - mode, - &WhenRecursive::Loop(*variant), - *variant, - tags, - false, - ); - Some(function) - } - - NonRecursive(tags) => { - let function = - modify_refcount_union(env, layout_ids, mode, when_recursive, tags); - - Some(function) - } + return Some(function); } - } - Closure(_, lambda_set, _) => { - if lambda_set.contains_refcounted() { - let function = modify_refcount_layout_build_function( - env, - parent, - layout_ids, - mode, - when_recursive, - &lambda_set.runtime_representation(), - )?; + let function = build_rec_union( + env, + layout_ids, + mode, + &WhenRecursive::Loop(*variant), + *variant, + ); - Some(function) - } else { - None - } + Some(function) } Struct(layouts) => { @@ -771,14 +703,14 @@ fn modify_refcount_list<'a, 'ctx, 'env>( &env.interns, "increment_list", "decrement_list", - &layout, + layout, mode, ); let function = match env.module.get_function(fn_name.as_str()) { Some(function_value) => function_value, None => { - let basic_type = basic_type_from_layout(env, &layout); + let basic_type = basic_type_from_layout(env, layout); let function_value = build_header(env, basic_type, mode, &fn_name); modify_refcount_list_help( @@ -832,7 +764,7 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>( let arg_symbol = Symbol::ARG_1; let arg_val = fn_val.get_param_iter().next().unwrap(); - arg_val.set_name(arg_symbol.ident_string(&env.interns)); + arg_val.set_name(arg_symbol.as_str(&env.interns)); let parent = fn_val; let original_wrapper = arg_val.into_struct_value(); @@ -908,14 +840,14 @@ fn modify_refcount_str<'a, 'ctx, 'env>( &env.interns, "increment_str", "decrement_str", - &layout, + layout, mode, ); let function = match env.module.get_function(fn_name.as_str()) { Some(function_value) => function_value, None => { - let basic_type = basic_type_from_layout(env, &layout); + let basic_type = basic_type_from_layout(env, layout); let function_value = build_header(env, basic_type, mode, &fn_name); modify_refcount_str_help(env, mode, layout, function_value); @@ -951,7 +883,7 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>( let arg_symbol = Symbol::ARG_1; let arg_val = fn_val.get_param_iter().next().unwrap(); - arg_val.set_name(arg_symbol.ident_string(&env.interns)); + arg_val.set_name(arg_symbol.as_str(&env.interns)); let parent = fn_val; @@ -1007,14 +939,14 @@ fn modify_refcount_dict<'a, 'ctx, 'env>( &env.interns, "increment_dict", "decrement_dict", - &layout, + layout, mode, ); let function = match env.module.get_function(fn_name.as_str()) { Some(function_value) => function_value, None => { - let basic_type = basic_type_from_layout(env, &layout); + let basic_type = basic_type_from_layout(env, layout); let function_value = build_header(env, basic_type, mode, &fn_name); modify_refcount_dict_help( @@ -1070,7 +1002,7 @@ fn modify_refcount_dict_help<'a, 'ctx, 'env>( let arg_symbol = Symbol::ARG_1; let arg_val = fn_val.get_param_iter().next().unwrap(); - arg_val.set_name(arg_symbol.ident_string(&env.interns)); + arg_val.set_name(arg_symbol.as_str(&env.interns)); let parent = fn_val; @@ -1169,7 +1101,7 @@ pub fn build_header_help<'a, 'ctx, 'env>( FAST_CALL_CONV, // Because it's an internal-only function, it should use the fast calling convention. ); - let subprogram = env.new_subprogram(&fn_name); + let subprogram = env.new_subprogram(fn_name); fn_val.set_subprogram(subprogram); env.dibuilder.finalize(); @@ -1208,10 +1140,8 @@ fn build_rec_union<'a, 'ctx, 'env>( mode: Mode, when_recursive: &WhenRecursive<'a>, union_layout: UnionLayout<'a>, - tags: &'a [&'a [Layout<'a>]], - is_nullable: bool, ) -> FunctionValue<'ctx> { - let layout = Layout::Union(UnionLayout::Recursive(tags)); + let layout = Layout::Union(union_layout); let (_, fn_name) = function_name_from_mode( layout_ids, @@ -1228,7 +1158,7 @@ fn build_rec_union<'a, 'ctx, 'env>( let block = env.builder.get_insert_block().expect("to be in a function"); let di_location = env.builder.get_current_debug_location().unwrap(); - let basic_type = basic_type_from_layout(env, &Layout::Union(union_layout)); + let basic_type = basic_type_from_layout(env, &layout); let function_value = build_header(env, basic_type, mode, &fn_name); build_rec_union_help( @@ -1237,9 +1167,7 @@ fn build_rec_union<'a, 'ctx, 'env>( mode, when_recursive, union_layout, - tags, function_value, - is_nullable, ); env.builder.position_at_end(block); @@ -1260,10 +1188,10 @@ fn build_rec_union_help<'a, 'ctx, 'env>( mode: Mode, when_recursive: &WhenRecursive<'a>, union_layout: UnionLayout<'a>, - tags: &'a [&'a [roc_mono::layout::Layout<'a>]], fn_val: FunctionValue<'ctx>, - is_nullable: bool, ) { + let tags = union_layout_tags(env.arena, &union_layout); + let is_nullable = union_layout.is_nullable(); debug_assert!(!tags.is_empty()); let context = &env.context; @@ -1281,12 +1209,13 @@ fn build_rec_union_help<'a, 'ctx, 'env>( let arg_val = fn_val.get_param_iter().next().unwrap(); - arg_val.set_name(arg_symbol.ident_string(&env.interns)); + arg_val.set_name(arg_symbol.as_str(&env.interns)); let parent = fn_val; debug_assert!(arg_val.is_pointer_value()); - let value_ptr = arg_val.into_pointer_value(); + let current_tag_id = get_tag_id(env, fn_val, &union_layout, arg_val); + let value_ptr = tag_pointer_clear_tag_id(env, arg_val.into_pointer_value()); // to increment/decrement the cons-cell itself let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr); @@ -1351,14 +1280,21 @@ fn build_rec_union_help<'a, 'ctx, 'env>( union_layout, tags, value_ptr, + current_tag_id, refcount_ptr, do_recurse_block, + DecOrReuse::Dec, ) } } } } +enum DecOrReuse { + Dec, + Reuse, +} + #[allow(clippy::too_many_arguments)] fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, @@ -1369,8 +1305,10 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>( union_layout: UnionLayout<'a>, tags: &[&[Layout<'a>]], value_ptr: PointerValue<'ctx>, + current_tag_id: IntValue<'ctx>, refcount_ptr: PointerToRefcount<'ctx>, match_block: BasicBlock<'ctx>, + decrement_or_reuse: DecOrReuse, ) { let mode = Mode::Dec; let call_mode = mode_to_call_mode(decrement_fn, mode); @@ -1442,28 +1380,8 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>( debug_assert!(ptr_as_i64_ptr.is_pointer_value()); // therefore we must cast it to our desired type - let union_type = match union_layout { - UnionLayout::NonRecursive(_) => unreachable!(), - UnionLayout::Recursive(_) | UnionLayout::NullableWrapped { .. } => { - union_data_block_of_memory( - env.context, - tag_id_int_type, - tags, - env.ptr_bytes, - ) - .into() - } - UnionLayout::NonNullableUnwrapped { .. } - | UnionLayout::NullableUnwrapped { .. } => { - block_of_memory_slices(env.context, tags, env.ptr_bytes) - } - }; - - let recursive_field_ptr = cast_basic_basic( - env.builder, - ptr_as_i64_ptr, - union_type.ptr_type(AddressSpace::Generic).into(), - ); + let union_type = basic_type_from_layout(env, &Layout::Union(union_layout)); + let recursive_field_ptr = cast_basic_basic(env.builder, ptr_as_i64_ptr, union_type); deferred_rec.push(recursive_field_ptr); } else if field_layout.contains_refcounted() { @@ -1486,7 +1404,13 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>( // lists. To achieve it, we must first load all fields that we want to inc/dec (done above) // and store them on the stack, then modify (and potentially free) the current cell, then // actually inc/dec the fields. - refcount_ptr.modify(call_mode, &Layout::Union(union_layout), env); + + match decrement_or_reuse { + DecOrReuse::Reuse => {} + DecOrReuse::Dec => { + refcount_ptr.modify(call_mode, &Layout::Union(union_layout), env); + } + } for (field, field_layout) in deferred_nonrec { modify_refcount_layout_help( @@ -1524,25 +1448,182 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>( let (_, only_branch) = cases.pop().unwrap(); env.builder.build_unconditional_branch(only_branch); } else { - // read the tag_id - let current_tag_id = get_tag_id(env, parent, &union_layout, value_ptr.into()); - - let merge_block = env.context.append_basic_block(parent, "decrement_merge"); + let default_block = env.context.append_basic_block(parent, "switch_default"); // switch on it env.builder - .build_switch(current_tag_id, merge_block, &cases); + .build_switch(current_tag_id, default_block, &cases); - env.builder.position_at_end(merge_block); + { + env.builder.position_at_end(default_block); - // increment/decrement the cons-cell itself - refcount_ptr.modify(call_mode, &Layout::Union(union_layout), env); + // increment/decrement the cons-cell itself + if let DecOrReuse::Dec = decrement_or_reuse { + refcount_ptr.modify(call_mode, &Layout::Union(union_layout), env); + } + } // this function returns void builder.build_return(None); } } +fn union_layout_tags<'a>( + arena: &'a bumpalo::Bump, + union_layout: &UnionLayout<'a>, +) -> &'a [&'a [Layout<'a>]] { + use UnionLayout::*; + + match union_layout { + NullableWrapped { + other_tags: tags, .. + } => *tags, + NullableUnwrapped { other_fields, .. } => arena.alloc([*other_fields]), + NonNullableUnwrapped(fields) => arena.alloc([*fields]), + Recursive(tags) => tags, + NonRecursive(tags) => tags, + } +} + +pub fn build_reset<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + union_layout: UnionLayout<'a>, +) -> FunctionValue<'ctx> { + let mode = Mode::Dec; + + let layout_id = layout_ids.get(Symbol::DEC, &Layout::Union(union_layout)); + let fn_name = layout_id.to_symbol_string(Symbol::DEC, &env.interns); + let fn_name = format!("{}_reset", fn_name); + + let when_recursive = WhenRecursive::Loop(union_layout); + let dec_function = build_rec_union(env, layout_ids, Mode::Dec, &when_recursive, union_layout); + + let function = match env.module.get_function(fn_name.as_str()) { + Some(function_value) => function_value, + None => { + let block = env.builder.get_insert_block().expect("to be in a function"); + let di_location = env.builder.get_current_debug_location().unwrap(); + + let basic_type = basic_type_from_layout(env, &Layout::Union(union_layout)); + let function_value = build_header(env, basic_type, mode, &fn_name); + + build_reuse_rec_union_help( + env, + layout_ids, + &when_recursive, + union_layout, + function_value, + dec_function, + ); + + env.builder.position_at_end(block); + env.builder + .set_current_debug_location(env.context, di_location); + + function_value + } + }; + + function +} + +#[allow(clippy::too_many_arguments)] +fn build_reuse_rec_union_help<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + when_recursive: &WhenRecursive<'a>, + union_layout: UnionLayout<'a>, + reset_function: FunctionValue<'ctx>, + dec_function: FunctionValue<'ctx>, +) { + let tags = union_layout_tags(env.arena, &union_layout); + let is_nullable = union_layout.is_nullable(); + + debug_assert!(!tags.is_empty()); + + let context = &env.context; + let builder = env.builder; + + // Add a basic block for the entry point + let entry = context.append_basic_block(reset_function, "entry"); + + builder.position_at_end(entry); + + debug_info_init!(env, reset_function); + + // Add args to scope + let arg_symbol = Symbol::ARG_1; + + let arg_val = reset_function.get_param_iter().next().unwrap(); + + arg_val.set_name(arg_symbol.as_str(&env.interns)); + + let parent = reset_function; + + debug_assert!(arg_val.is_pointer_value()); + let current_tag_id = get_tag_id(env, reset_function, &union_layout, arg_val); + let value_ptr = tag_pointer_clear_tag_id(env, arg_val.into_pointer_value()); + + // to increment/decrement the cons-cell itself + let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr); + let call_mode = CallMode::Dec; + + let should_recurse_block = env.context.append_basic_block(parent, "should_recurse"); + + let ctx = env.context; + if is_nullable { + let is_null = env.builder.build_is_null(value_ptr, "is_null"); + + let then_block = ctx.append_basic_block(parent, "then"); + + env.builder + .build_conditional_branch(is_null, then_block, should_recurse_block); + + { + env.builder.position_at_end(then_block); + env.builder.build_return(None); + } + } else { + env.builder.build_unconditional_branch(should_recurse_block); + } + + env.builder.position_at_end(should_recurse_block); + + let layout = Layout::Union(union_layout); + + let do_recurse_block = env.context.append_basic_block(parent, "do_recurse"); + let no_recurse_block = env.context.append_basic_block(parent, "no_recurse"); + + builder.build_conditional_branch(refcount_ptr.is_1(env), do_recurse_block, no_recurse_block); + + { + env.builder.position_at_end(no_recurse_block); + + refcount_ptr.modify(call_mode, &layout, env); + env.builder.build_return(None); + } + + { + env.builder.position_at_end(do_recurse_block); + + build_rec_union_recursive_decrement( + env, + layout_ids, + when_recursive, + parent, + dec_function, + union_layout, + tags, + value_ptr, + current_tag_id, + refcount_ptr, + do_recurse_block, + DecOrReuse::Reuse, + ) + } +} + fn function_name_from_mode<'a>( layout_ids: &mut LayoutIds<'a>, interns: &Interns, @@ -1634,7 +1715,7 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>( let arg_symbol = Symbol::ARG_1; let arg_val = fn_val.get_param_iter().next().unwrap(); - arg_val.set_name(arg_symbol.ident_string(&env.interns)); + arg_val.set_name(arg_symbol.as_str(&env.interns)); let parent = fn_val; diff --git a/compiler/ident/Cargo.toml b/compiler/ident/Cargo.toml new file mode 100644 index 0000000000..d272f4818f --- /dev/null +++ b/compiler/ident/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "roc_ident" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" diff --git a/compiler/ident/src/lib.rs b/compiler/ident/src/lib.rs new file mode 100644 index 0000000000..40a10200a6 --- /dev/null +++ b/compiler/ident/src/lib.rs @@ -0,0 +1,430 @@ +#![warn(clippy::dbg_macro)] + +use core::cmp::Ordering; +use core::convert::From; +use core::{fmt, mem, ptr, slice}; +use std::alloc::{GlobalAlloc, Layout, System}; + +/// A string which can store identifiers using the small string optimization. +/// It relies on the invariant that it cannot store null characters to store +/// an extra character; if the last byte is 0, that means it's a large string. +/// +/// Because the msbyte of the length is always 0, this can only store up to +/// 2^56 bytes on a 64-bit target, or 2^28 bytes in a 32-bit target. That's +/// way more than enough for an identifier! +/// +/// If it's a small string, that discriminant byte is used to store the length, +/// except it stores it as (255 - length) so that it will be in the range +/// 192 - 255 (all of which are invalid UTF-8 when in the final position of +/// a UTF-8 string). This design works on little-endian targets, but a different +/// design for storing length might be necessary on big-endian targets. + +// For big-endian, field order must be swapped! +// Otherwise, the discriminant byte will be in the wrong place. +#[cfg(target_endian = "little")] +#[repr(C)] +pub struct IdentStr { + elements: *const u8, + length: usize, +} + +impl IdentStr { + pub fn len(&self) -> usize { + let bytes = self.length.to_ne_bytes(); + let last_byte = bytes[mem::size_of::() - 1]; + + // We always perform this subtraction so that the following + // conditionals can all be cmov instructions. + let small_str_variable_len = (u8::MAX - last_byte) as usize; + + // The numbers 192 - 255 (0xC0 - 0xFF) are not valid as the final + // byte of a UTF-8 string. Hence they are unused and we can use them + // to store the length of a small string! + // + // Reference: https://en.wikipedia.org/wiki/UTF-8#Codepage_layout + if last_byte >= 0xC0 { + small_str_variable_len + } else if last_byte == 0 { + // This is a big string, so return its length. + self.length + } else { + // This is a valid UTF-8 character, meaning the entire struct must + // be in use for storing characters. + mem::size_of::() + } + } + + pub fn is_empty(&self) -> bool { + self.length == 0 + } + + pub fn is_small_str(&self) -> bool { + let bytes = self.length.to_ne_bytes(); + let last_byte = bytes[mem::size_of::() - 1]; + + last_byte != 0 + } + + pub fn get(&self, index: usize) -> Option<&u8> { + if index < self.len() { + Some(unsafe { + let raw = if self.is_small_str() { + self.get_small_str_ptr().add(index) + } else { + self.elements.add(index) + }; + + &*raw + }) + } else { + None + } + } + + pub fn get_bytes(&self) -> *const u8 { + if self.is_small_str() { + self.get_small_str_ptr() + } else { + self.elements + } + } + + fn get_small_str_ptr(&self) -> *const u8 { + (self as *const IdentStr).cast() + } + + fn from_slice(slice: &[u8]) -> Self { + let len = slice.len(); + + match len.cmp(&mem::size_of::()) { + Ordering::Less => { + // This fits in a small string, but needs its length recorded + let mut answer_bytes: [u8; mem::size_of::()] = unsafe { + mem::transmute::()]>(Self::default()) + }; + + // Copy the bytes from the slice into the answer + let dest_slice = + unsafe { slice::from_raw_parts_mut(&mut answer_bytes as *mut u8, len) }; + + dest_slice.copy_from_slice(slice); + + let mut answer: Self = + unsafe { mem::transmute::<[u8; mem::size_of::()], Self>(answer_bytes) }; + + // Write length and small string bit to last byte of length. + { + let mut bytes = answer.length.to_ne_bytes(); + + bytes[mem::size_of::() - 1] = u8::MAX - len as u8; + + answer.length = usize::from_ne_bytes(bytes); + } + + answer + } + Ordering::Equal => { + // This fits in a small string, and is exactly long enough to + // take up the entire available struct + let mut answer_bytes: [u8; mem::size_of::()] = unsafe { + mem::transmute::()]>(Self::default()) + }; + + // Copy the bytes from the slice into the answer + let dest_slice = unsafe { + slice::from_raw_parts_mut(&mut answer_bytes as *mut u8, mem::size_of::()) + }; + + dest_slice.copy_from_slice(slice); + + unsafe { mem::transmute::<[u8; mem::size_of::()], Self>(answer_bytes) } + } + Ordering::Greater => { + // This needs a big string + let elements = unsafe { + let align = mem::align_of::(); + let layout = Layout::from_size_align_unchecked(len, align); + + System.alloc(layout) + }; + + // Turn the new elements into a slice, and copy the existing + // slice's bytes into it. + unsafe { + let dest_slice = slice::from_raw_parts_mut(elements, len); + + dest_slice.copy_from_slice(slice); + } + + Self { + length: len, + elements, + } + } + } + } + + pub fn as_slice(&self) -> &[u8] { + use core::slice::from_raw_parts; + + if self.is_small_str() { + unsafe { from_raw_parts(self.get_small_str_ptr(), self.len()) } + } else { + unsafe { from_raw_parts(self.elements, self.length) } + } + } + + pub fn as_str(&self) -> &str { + let slice = self.as_slice(); + + unsafe { core::str::from_utf8_unchecked(slice) } + } + + /// Write a CStr (null-terminated) representation of this IdentStr into + /// the given buffer. + /// + /// # Safety + /// This assumes the given buffer has enough space, so make sure you only + /// pass in a pointer to an allocation that's at least as long as this Str! + pub unsafe fn write_c_str(&self, buf: *mut char) { + if self.is_small_str() { + ptr::copy_nonoverlapping(self.get_small_str_ptr(), buf as *mut u8, self.len()); + } else { + ptr::copy_nonoverlapping(self.elements, buf as *mut u8, self.len()); + } + + // null-terminate + *(buf.add(self.len())) = '\0'; + } +} + +impl Default for IdentStr { + fn default() -> Self { + Self { + length: 0, + elements: core::ptr::null_mut(), + } + } +} + +impl std::ops::Deref for IdentStr { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.as_str() + } +} + +impl From<&str> for IdentStr { + fn from(str: &str) -> Self { + Self::from_slice(str.as_bytes()) + } +} + +impl From for IdentStr { + fn from(str: String) -> Self { + Self::from_slice(str.as_bytes()) + } +} + +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("string", &self.as_str()) + .field("elements", &self.as_slice()) + .finish() + } +} + +impl fmt::Display 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.write_str(self.as_str()) + } +} + +unsafe impl std::marker::Sync for IdentStr {} +unsafe impl std::marker::Send for IdentStr {} + +impl PartialEq for IdentStr { + fn eq(&self, other: &Self) -> bool { + self.as_slice() == other.as_slice() + } +} + +impl Eq for IdentStr {} + +impl PartialOrd for IdentStr { + fn partial_cmp(&self, other: &Self) -> Option { + self.as_str().partial_cmp(other.as_str()) + } +} + +impl Ord for IdentStr { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_str().cmp(other.as_str()) + } +} + +impl std::hash::Hash for IdentStr { + fn hash(&self, hasher: &mut H) + where + H: std::hash::Hasher, + { + self.as_str().hash(hasher) + } +} + +impl Clone for IdentStr { + fn clone(&self) -> Self { + if self.is_small_str() || self.is_empty() { + Self { + elements: self.elements, + length: self.length, + } + } else { + let capacity_size = core::mem::size_of::(); + let copy_length = self.length + capacity_size; + let elements = unsafe { + let align = mem::align_of::(); + let layout = Layout::from_size_align_unchecked(copy_length, align); + let raw_ptr = System.alloc(layout); + + let dest_slice = slice::from_raw_parts_mut(raw_ptr, copy_length); + let src_ptr = self.elements as *mut u8; + let src_slice = slice::from_raw_parts(src_ptr, copy_length); + + dest_slice.copy_from_slice(src_slice); + + raw_ptr as *mut u8 + }; + + Self { + elements, + length: self.length, + } + } + } +} + +impl Drop for IdentStr { + fn drop(&mut self) { + if !self.is_small_str() { + unsafe { + let align = mem::align_of::(); + let layout = Layout::from_size_align_unchecked(self.length, align); + + System.dealloc(self.elements as *mut _, layout); + } + } + } +} + +#[test] +fn default() { + let answer = IdentStr::default(); + + assert_eq!(answer.len(), 0); + assert_eq!(answer, answer); + assert_eq!(answer.clone(), answer); + assert_eq!(answer.clone(), answer.clone()); + assert_eq!(answer.as_str(), ""); + assert_eq!(answer.clone().as_str(), ""); +} + +#[test] +fn big_str() { + for &string in &[ + "0123456789abcdefg", + "0123456789abcdefgh", + "0123456789abcdefghi", + ] { + let answer = IdentStr::from(string); + + assert_eq!(answer.len(), string.len()); + assert_eq!(answer, answer); + assert_eq!(answer.clone(), answer); + assert_eq!(answer.clone(), answer.clone()); + assert_eq!(answer.as_str(), string); + assert_eq!(answer.clone().as_str(), string); + } +} + +#[cfg(target_pointer_width = "64")] +#[test] +fn small_var_length() { + for &string in &[ + "", + "0", + "01", + "012", + "0123", + "01234", + "012345", + "0123456", + "01234567", + "012345678", + "0123456789", + "0123456789a", + "0123456789ab", + "0123456789abc", + "0123456789abcd", + "0123456789abcde ", + ] { + let answer = IdentStr::from(string); + + assert_eq!(answer.len(), string.len()); + assert_eq!(answer, answer); + assert_eq!(answer.clone(), answer); + assert_eq!(answer.clone(), answer.clone()); + assert_eq!(answer.as_str(), string); + assert_eq!(answer.clone().as_str(), string); + } +} + +#[cfg(target_pointer_width = "32")] +#[test] +fn small_var_length() { + for &string in &[ + "", "0", "01", "012", "0123", "01234", "012345", "0123456", "01234567", + ] { + let answer = IdentStr::from(string); + + assert_eq!(answer.len(), string.len()); + assert_eq!(answer, answer); + assert_eq!(answer.clone(), answer); + assert_eq!(answer.clone(), answer.clone()); + assert_eq!(answer.as_str(), string); + assert_eq!(answer.clone().as_str(), string); + } +} + +#[cfg(target_pointer_width = "64")] +#[test] +fn small_max_length() { + let string = "0123456789abcdef"; + let answer = IdentStr::from(string); + + assert_eq!(answer.len(), string.len()); + assert_eq!(answer, answer); + assert_eq!(answer.clone(), answer); + assert_eq!(answer.clone(), answer.clone()); + assert_eq!(answer.as_str(), string); + assert_eq!(answer.clone().as_str(), string); +} + +#[cfg(target_pointer_width = "32")] +#[test] +fn small_max_length() { + let string = "01234567"; + let answer = IdentStr::from(string); + + assert_eq!(answer.len(), string.len()); + assert_eq!(answer, answer); + assert_eq!(answer.clone(), answer); + assert_eq!(answer.clone(), answer.clone()); + assert_eq!(answer.as_str(), string); + assert_eq!(answer.clone().as_str(), string); +} diff --git a/compiler/load/Cargo.toml b/compiler/load/Cargo.toml index 6ed6e3cc63..447fe8d409 100644 --- a/compiler/load/Cargo.toml +++ b/compiler/load/Cargo.toml @@ -22,7 +22,6 @@ roc_reporting = { path = "../reporting" } morphic_lib = { path = "../../vendor/morphic_lib" } ven_pretty = { path = "../../vendor/pretty" } bumpalo = { version = "3.6.1", features = ["collections"] } -inlinable_string = "0.1" parking_lot = { version = "0.11", features = ["deadlock_detection"] } crossbeam = "0.7" num_cpus = "1" diff --git a/compiler/load/src/docs.rs b/compiler/load/src/docs.rs index 1fa5db439d..6b8919f058 100644 --- a/compiler/load/src/docs.rs +++ b/compiler/load/src/docs.rs @@ -3,7 +3,6 @@ use crate::docs::TypeAnnotation::{ Apply, BoundVariable, Function, NoTypeAnn, ObscuredRecord, ObscuredTagUnion, Record, TagUnion, }; use crate::file::LoadedModule; -use inlinable_string::InlinableString; use roc_can::scope::Scope; use roc_module::ident::ModuleName; use roc_module::symbol::IdentIds; @@ -148,7 +147,7 @@ fn generate_entry_doc<'a>( match def { Def::SpaceBefore(sub_def, comments_or_new_lines) => { - // Comments before a definition are attached to the current defition + // Comments before a definition are attached to the current definition for detached_doc in detached_docs_from_comments_and_new_lines(comments_or_new_lines) { acc.push(DetachedDoc(detached_doc)); @@ -166,16 +165,14 @@ fn generate_entry_doc<'a>( (new_acc, Some(comments_or_new_lines)) } - Def::Annotation(loc_pattern, _loc_ann) => match loc_pattern.value { + Def::Annotation(loc_pattern, loc_ann) => match loc_pattern.value { Pattern::Identifier(identifier) => { // Check if the definition is exposed - if ident_ids - .get_id(&InlinableString::from(identifier)) - .is_some() - { + if ident_ids.get_id(&identifier.into()).is_some() { + let name = identifier.to_string(); let doc_def = DocDef { - name: identifier.to_string(), - type_annotation: NoTypeAnn, + name, + type_annotation: type_to_docs(false, loc_ann.value), type_vars: Vec::new(), docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs), }; @@ -193,10 +190,7 @@ fn generate_entry_doc<'a>( } => match ann_pattern.value { Pattern::Identifier(identifier) => { // Check if the definition is exposed - if ident_ids - .get_id(&InlinableString::from(identifier)) - .is_some() - { + if ident_ids.get_id(&identifier.into()).is_some() { let doc_def = DocDef { name: identifier.to_string(), type_annotation: type_to_docs(false, ann_type.value), diff --git a/compiler/load/src/effect_module.rs b/compiler/load/src/effect_module.rs index 5dab90a4f8..f6081e6e21 100644 --- a/compiler/load/src/effect_module.rs +++ b/compiler/load/src/effect_module.rs @@ -1,3 +1,4 @@ +use roc_can::annotation::IntroducedVariables; use roc_can::def::{Declaration, Def}; use roc_can::env::Env; use roc_can::expr::{Expr, Recursive}; @@ -160,25 +161,22 @@ fn build_effect_always( (function_var, closure) }; - use roc_can::annotation::IntroducedVariables; - let mut introduced_variables = IntroducedVariables::default(); let signature = { // Effect.always : a -> Effect a let var_a = var_store.fresh(); - introduced_variables.insert_named("a".into(), var_a); - let effect_a = { - let actual = build_effect_actual(effect_tag_name, Type::Variable(var_a), var_store); - - Type::Alias( - effect_symbol, - vec![("a".into(), Type::Variable(var_a))], - Box::new(actual), - ) - }; + let effect_a = build_effect_alias( + effect_symbol, + effect_tag_name, + "a", + var_a, + Type::Variable(var_a), + var_store, + &mut introduced_variables, + ); let closure_var = var_store.fresh(); introduced_variables.insert_wildcard(closure_var); @@ -353,8 +351,6 @@ fn build_effect_map( loc_body: Box::new(Located::at_zero(body)), }; - use roc_can::annotation::IntroducedVariables; - let mut introduced_variables = IntroducedVariables::default(); let signature = { @@ -365,26 +361,25 @@ fn build_effect_map( introduced_variables.insert_named("a".into(), var_a); introduced_variables.insert_named("b".into(), var_b); - let effect_a = { - let actual = - build_effect_actual(effect_tag_name.clone(), Type::Variable(var_a), var_store); + let effect_a = build_effect_alias( + effect_symbol, + effect_tag_name.clone(), + "a", + var_a, + Type::Variable(var_a), + var_store, + &mut introduced_variables, + ); - Type::Alias( - effect_symbol, - vec![("a".into(), Type::Variable(var_a))], - Box::new(actual), - ) - }; - - let effect_b = { - let actual = build_effect_actual(effect_tag_name, Type::Variable(var_b), var_store); - - Type::Alias( - effect_symbol, - vec![("b".into(), Type::Variable(var_b))], - Box::new(actual), - ) - }; + let effect_b = build_effect_alias( + effect_symbol, + effect_tag_name, + "b", + var_b, + Type::Variable(var_b), + var_store, + &mut introduced_variables, + ); let closure_var = var_store.fresh(); introduced_variables.insert_wildcard(closure_var); @@ -526,8 +521,6 @@ fn build_effect_after( loc_body: Box::new(Located::at_zero(to_effect_call)), }; - use roc_can::annotation::IntroducedVariables; - let mut introduced_variables = IntroducedVariables::default(); let signature = { @@ -537,26 +530,25 @@ fn build_effect_after( introduced_variables.insert_named("a".into(), var_a); introduced_variables.insert_named("b".into(), var_b); - let effect_a = { - let actual = - build_effect_actual(effect_tag_name.clone(), Type::Variable(var_a), var_store); + let effect_a = build_effect_alias( + effect_symbol, + effect_tag_name.clone(), + "a", + var_a, + Type::Variable(var_a), + var_store, + &mut introduced_variables, + ); - Type::Alias( - effect_symbol, - vec![("a".into(), Type::Variable(var_a))], - Box::new(actual), - ) - }; - - let effect_b = { - let actual = build_effect_actual(effect_tag_name, Type::Variable(var_b), var_store); - - Type::Alias( - effect_symbol, - vec![("b".into(), Type::Variable(var_b))], - Box::new(actual), - ) - }; + let effect_b = build_effect_alias( + effect_symbol, + effect_tag_name, + "b", + var_b, + Type::Variable(var_b), + var_store, + &mut introduced_variables, + ); let closure_var = var_store.fresh(); introduced_variables.insert_wildcard(closure_var); @@ -763,6 +755,40 @@ pub fn build_host_exposed_def( } } +fn build_effect_alias( + effect_symbol: Symbol, + effect_tag_name: TagName, + a_name: &str, + a_var: Variable, + a_type: Type, + var_store: &mut VarStore, + introduced_variables: &mut IntroducedVariables, +) -> Type { + let closure_var = var_store.fresh(); + introduced_variables.insert_wildcard(closure_var); + + let actual = { + Type::TagUnion( + vec![( + effect_tag_name, + vec![Type::Function( + vec![Type::EmptyRec], + Box::new(Type::Variable(closure_var)), + Box::new(a_type), + )], + )], + Box::new(Type::EmptyTagUnion), + ) + }; + + Type::Alias { + symbol: effect_symbol, + type_arguments: vec![(a_name.into(), Type::Variable(a_var))], + lambda_set_variables: vec![roc_types::types::LambdaSet(Type::Variable(closure_var))], + actual: Box::new(actual), + } +} + pub fn build_effect_actual( effect_tag_name: TagName, a_type: Type, diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 24d07b1a47..639f3a3cd6 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -225,7 +225,7 @@ impl<'a> Dependencies<'a> { if let Some(to_notify) = self.notifies.get(&key) { for notify_key in to_notify { let mut is_empty = false; - if let Some(waiting_for_pairs) = self.waiting_for.get_mut(¬ify_key) { + if let Some(waiting_for_pairs) = self.waiting_for.get_mut(notify_key) { waiting_for_pairs.remove(&key); is_empty = waiting_for_pairs.is_empty(); } @@ -469,7 +469,7 @@ fn start_phase<'a>( for dep_id in deps_by_name.values() { // We already verified that these are all present, // so unwrapping should always succeed here. - let idents = ident_ids_by_module.get(&dep_id).unwrap(); + let idents = ident_ids_by_module.get(dep_id).unwrap(); dep_idents.insert(*dep_id, idents.clone()); } @@ -524,6 +524,7 @@ fn start_phase<'a>( var_store, imported_modules, declarations, + dep_idents, .. } = constrained; @@ -535,7 +536,8 @@ fn start_phase<'a>( var_store, imported_modules, &mut state.exposed_types, - &state.stdlib, + state.stdlib, + dep_idents, declarations, ) } @@ -621,6 +623,7 @@ pub struct LoadedModule { pub type_problems: MutMap>, pub declarations_by_id: MutMap>, pub exposed_to_host: MutMap, + pub dep_idents: MutMap, pub exposed_aliases: MutMap, pub exposed_values: Vec, pub header_sources: MutMap)>, @@ -676,6 +679,7 @@ struct ConstrainedModule { constraint: Constraint, ident_ids: IdentIds, var_store: VarStore, + dep_idents: MutMap, module_timing: ModuleTiming, } @@ -759,6 +763,7 @@ enum Msg<'a> { solved_module: SolvedModule, solved_subs: Solved, decls: Vec, + dep_idents: MutMap, module_timing: ModuleTiming, unused_imports: MutMap, }, @@ -767,6 +772,7 @@ enum Msg<'a> { exposed_vars_by_symbol: MutMap, exposed_aliases_by_symbol: MutMap, exposed_values: Vec, + dep_idents: MutMap, documentation: MutMap, }, FoundSpecializations { @@ -985,6 +991,7 @@ enum BuildTask<'a> { constraint: Constraint, var_store: VarStore, declarations: Vec, + dep_idents: MutMap, unused_imports: MutMap, }, BuildPendingSpecializations { @@ -1516,6 +1523,7 @@ where exposed_vars_by_symbol, exposed_aliases_by_symbol, exposed_values, + dep_idents, documentation, } => { // We're done! There should be no more messages pending. @@ -1534,6 +1542,7 @@ where exposed_values, exposed_aliases_by_symbol, exposed_vars_by_symbol, + dep_idents, documentation, ))); } @@ -1635,7 +1644,7 @@ fn start_tasks<'a>( ) -> Result<(), LoadingProblem<'a>> { for (module_id, phase) in work { for task in start_phase(module_id, phase, arena, state) { - enqueue_task(&injector, worker_listeners, task)? + enqueue_task(injector, worker_listeners, task)? } } @@ -1759,11 +1768,11 @@ fn update<'a>( state.module_cache.headers.insert(header.module_id, header); - start_tasks(arena, &mut state, work, &injector, worker_listeners)?; + start_tasks(arena, &mut state, work, injector, worker_listeners)?; let work = state.dependencies.notify(home, Phase::LoadHeader); - start_tasks(arena, &mut state, work, &injector, worker_listeners)?; + start_tasks(arena, &mut state, work, injector, worker_listeners)?; Ok(state) } @@ -1796,7 +1805,7 @@ fn update<'a>( let work = state.dependencies.notify(module_id, Phase::Parse); - start_tasks(arena, &mut state, work, &injector, worker_listeners)?; + start_tasks(arena, &mut state, work, injector, worker_listeners)?; Ok(state) } @@ -1831,7 +1840,7 @@ fn update<'a>( .dependencies .notify(module_id, Phase::CanonicalizeAndConstrain); - start_tasks(arena, &mut state, work, &injector, worker_listeners)?; + start_tasks(arena, &mut state, work, injector, worker_listeners)?; Ok(state) } @@ -1882,7 +1891,7 @@ fn update<'a>( .notify(module_id, Phase::CanonicalizeAndConstrain), ); - start_tasks(arena, &mut state, work, &injector, worker_listeners)?; + start_tasks(arena, &mut state, work, injector, worker_listeners)?; Ok(state) } @@ -1892,6 +1901,7 @@ fn update<'a>( solved_module, solved_subs, decls, + dep_idents, mut module_timing, mut unused_imports, } => { @@ -1949,6 +1959,7 @@ fn update<'a>( exposed_vars_by_symbol: solved_module.exposed_vars_by_symbol, exposed_values: solved_module.exposed_symbols, exposed_aliases_by_symbol: solved_module.aliases, + dep_idents, documentation, }) .map_err(|_| LoadingProblem::MsgChannelDied)?; @@ -1986,7 +1997,7 @@ fn update<'a>( state.constrained_ident_ids.insert(module_id, ident_ids); } - start_tasks(arena, &mut state, work, &injector, worker_listeners)?; + start_tasks(arena, &mut state, work, injector, worker_listeners)?; } Ok(state) @@ -2041,13 +2052,13 @@ fn update<'a>( .dependencies .notify(module_id, Phase::FindSpecializations); - start_tasks(arena, &mut state, work, &injector, worker_listeners)?; + start_tasks(arena, &mut state, work, injector, worker_listeners)?; Ok(state) } MadeSpecializations { module_id, - ident_ids, + mut ident_ids, subs, procedures, external_specializations_requested, @@ -2070,6 +2081,15 @@ fn update<'a>( && state.dependencies.solved_all() && state.goal_phase == Phase::MakeSpecializations { + Proc::insert_reset_reuse_operations( + arena, + module_id, + &mut ident_ids, + &mut state.procedures, + ); + + Proc::insert_refcount_operations(arena, &mut state.procedures); + // display the mono IR of the module, for debug purposes if roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS { let procs_string = state @@ -2083,8 +2103,6 @@ fn update<'a>( println!("{}", result); } - Proc::insert_refcount_operations(arena, &mut state.procedures); - // This is not safe with the new non-recursive RC updates that we do for tag unions // // Proc::optimize_refcount_operations( @@ -2136,7 +2154,7 @@ fn update<'a>( existing.extend(requested); } - start_tasks(arena, &mut state, work, &injector, worker_listeners)?; + start_tasks(arena, &mut state, work, injector, worker_listeners)?; } Ok(state) @@ -2276,6 +2294,7 @@ fn finish( exposed_values: Vec, exposed_aliases_by_symbol: MutMap, exposed_vars_by_symbol: MutMap, + dep_idents: MutMap, documentation: MutMap, ) -> LoadedModule { let module_ids = Arc::try_unwrap(state.arc_modules) @@ -2309,6 +2328,7 @@ fn finish( can_problems: state.module_cache.can_problems, type_problems: state.module_cache.type_problems, declarations_by_id: state.declarations_by_id, + dep_idents, exposed_aliases: exposed_aliases_by_symbol, exposed_values, exposed_to_host: exposed_vars_by_symbol.into_iter().collect(), @@ -2341,7 +2361,7 @@ fn load_pkg_config<'a>( let parse_start = SystemTime::now(); let bytes = arena.alloc(bytes_vec); let parse_state = parser::State::new(bytes); - let parsed = roc_parse::module::parse_header(&arena, parse_state); + let parsed = roc_parse::module::parse_header(arena, parse_state); let parse_header_duration = parse_start.elapsed().unwrap(); // Insert the first entries for this module's timings @@ -2511,7 +2531,7 @@ fn parse_header<'a>( ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> { let parse_start = SystemTime::now(); let parse_state = parser::State::new(src_bytes); - let parsed = roc_parse::module::parse_header(&arena, parse_state); + let parsed = roc_parse::module::parse_header(arena, parse_state); let parse_header_duration = parse_start.elapsed().unwrap(); // Insert the first entries for this module's timings @@ -2660,7 +2680,7 @@ fn parse_header<'a>( } Ok((ast::Module::Platform { header }, _parse_state)) => Ok(fabricate_effects_module( arena, - &"", + "", module_ids, ident_ids_by_module, header, @@ -2817,10 +2837,8 @@ fn send_header<'a>( let mut ident_ids_by_module = (*ident_ids_by_module).lock(); let name = match opt_shorthand { - Some(shorthand) => { - PQModuleName::Qualified(&shorthand, declared_name.as_inline_str().clone()) - } - None => PQModuleName::Unqualified(declared_name.as_inline_str().clone()), + Some(shorthand) => PQModuleName::Qualified(shorthand, declared_name), + None => PQModuleName::Unqualified(declared_name), }; home = module_ids.get_or_insert(&name); @@ -2839,13 +2857,11 @@ fn send_header<'a>( let pq_module_name = match qualified_module_name.opt_package { None => match opt_shorthand { Some(shorthand) => { - PQModuleName::Qualified(shorthand, qualified_module_name.module.into()) + PQModuleName::Qualified(shorthand, qualified_module_name.module) } - None => PQModuleName::Unqualified(qualified_module_name.module.into()), + None => PQModuleName::Unqualified(qualified_module_name.module), }, - Some(package) => { - PQModuleName::Qualified(package, cloned_module_name.clone().into()) - } + Some(package) => PQModuleName::Qualified(package, cloned_module_name), }; let module_id = module_ids.get_or_insert(&pq_module_name); @@ -2861,7 +2877,7 @@ fn send_header<'a>( .or_insert_with(IdentIds::default); for ident in exposed_idents { - let ident_id = ident_ids.get_or_insert(ident.as_inline_str()); + let ident_id = ident_ids.get_or_insert(&ident); let symbol = Symbol::new(module_id, ident_id); // Since this value is exposed, add it to our module's default scope. @@ -2894,13 +2910,13 @@ fn send_header<'a>( } if cfg!(debug_assertions) { - home.register_debug_idents(&ident_ids); + home.register_debug_idents(ident_ids); } ident_ids.clone() }; - let mut parse_entries: Vec<_> = (&packages).iter().map(|x| &x.value).collect(); + let mut parse_entries: Vec<_> = packages.iter().map(|x| &x.value).collect(); let mut package_entries = MutMap::default(); while let Some(parse_entry) = parse_entries.pop() { @@ -2991,8 +3007,6 @@ fn send_header_two<'a>( ident_ids_by_module: Arc>>, module_timing: ModuleTiming, ) -> (ModuleId, Msg<'a>) { - use inlinable_string::InlinableString; - let PlatformHeaderInfo { filename, shorthand, @@ -3005,7 +3019,7 @@ fn send_header_two<'a>( imports, } = info; - let declared_name: InlinableString = "".into(); + let declared_name: ModuleName = "".into(); let mut imported: Vec<(QualifiedModuleName, Vec, Region)> = Vec::with_capacity(imports.len()); @@ -3046,7 +3060,7 @@ fn send_header_two<'a>( let mut module_ids = (*module_ids).lock(); let mut ident_ids_by_module = (*ident_ids_by_module).lock(); - let name = PQModuleName::Qualified(&shorthand, declared_name); + let name = PQModuleName::Qualified(shorthand, declared_name); home = module_ids.get_or_insert(&name); // Ensure this module has an entry in the exposed_ident_ids map. @@ -3062,10 +3076,8 @@ fn send_header_two<'a>( for (qualified_module_name, exposed_idents, region) in imported.into_iter() { let cloned_module_name = qualified_module_name.module.clone(); let pq_module_name = match qualified_module_name.opt_package { - None => PQModuleName::Qualified(shorthand, qualified_module_name.module.into()), - Some(package) => { - PQModuleName::Qualified(package, cloned_module_name.clone().into()) - } + None => PQModuleName::Qualified(shorthand, qualified_module_name.module), + Some(package) => PQModuleName::Qualified(package, cloned_module_name.clone()), }; let module_id = module_ids.get_or_insert(&pq_module_name); @@ -3081,7 +3093,7 @@ fn send_header_two<'a>( .or_insert_with(IdentIds::default); for ident in exposed_idents { - let ident_id = ident_ids.get_or_insert(ident.as_inline_str()); + let ident_id = ident_ids.get_or_insert(&ident); let symbol = Symbol::new(module_id, ident_id); // Since this value is exposed, add it to our module's default scope. @@ -3098,7 +3110,7 @@ fn send_header_two<'a>( for (loc_ident, _) in unpack_exposes_entries(arena, requires) { let ident: Ident = loc_ident.value.into(); - let ident_id = ident_ids.get_or_insert(ident.as_inline_str()); + let ident_id = ident_ids.get_or_insert(&ident); let symbol = Symbol::new(app_module_id, ident_id); // Since this value is exposed, add it to our module's default scope. @@ -3131,13 +3143,13 @@ fn send_header_two<'a>( } if cfg!(debug_assertions) { - home.register_debug_idents(&ident_ids); + home.register_debug_idents(ident_ids); } ident_ids.clone() }; - let mut parse_entries: Vec<_> = (&packages).iter().map(|x| &x.value).collect(); + let mut parse_entries: Vec<_> = packages.iter().map(|x| &x.value).collect(); let mut package_entries = MutMap::default(); while let Some(parse_entry) = parse_entries.pop() { @@ -3165,7 +3177,7 @@ fn send_header_two<'a>( let module_name = ModuleNameEnum::PkgConfig; let main_for_host = { - let ident_str: InlinableString = provides[0].value.as_str().into(); + let ident_str: Ident = provides[0].value.as_str().into(); let ident_id = ident_ids.get_or_insert(&ident_str); Symbol::new(home, ident_id) @@ -3227,6 +3239,7 @@ impl<'a> BuildTask<'a> { imported_modules: MutMap, exposed_types: &mut SubsByModule, stdlib: &StdLib, + dep_idents: MutMap, declarations: Vec, ) -> Self { let home = module.module_id; @@ -3254,6 +3267,7 @@ impl<'a> BuildTask<'a> { constraint, var_store, declarations, + dep_idents, module_timing, unused_imports, } @@ -3269,6 +3283,7 @@ fn run_solve<'a>( constraint: Constraint, mut var_store: VarStore, decls: Vec, + dep_idents: MutMap, unused_imports: MutMap, ) -> Msg<'a> { // We have more constraining work to do now, so we'll add it to our timings. @@ -3323,6 +3338,7 @@ fn run_solve<'a>( solved_subs, ident_ids, decls, + dep_idents, solved_module, module_timing, unused_imports, @@ -3383,7 +3399,7 @@ fn fabricate_effects_module<'a>( let module_id: ModuleId; - let effect_entries = unpack_exposes_entries(arena, &effects.entries); + let effect_entries = unpack_exposes_entries(arena, effects.entries); let name = effects.effect_type_name; let declared_name: ModuleName = name.into(); @@ -3402,7 +3418,10 @@ fn fabricate_effects_module<'a>( for exposed in header.exposes { if let ExposesEntry::Exposed(module_name) = exposed.value { - module_ids.get_or_insert(&PQModuleName::Qualified(shorthand, module_name.into())); + module_ids.get_or_insert(&PQModuleName::Qualified( + shorthand, + module_name.as_str().into(), + )); } } } @@ -3412,7 +3431,7 @@ fn fabricate_effects_module<'a>( let mut module_ids = (*module_ids).lock(); let mut ident_ids_by_module = (*ident_ids_by_module).lock(); - let name = PQModuleName::Qualified(shorthand, declared_name.as_inline_str().clone()); + let name = PQModuleName::Qualified(shorthand, declared_name); module_id = module_ids.get_or_insert(&name); // Ensure this module has an entry in the exposed_ident_ids map. @@ -3457,7 +3476,7 @@ fn fabricate_effects_module<'a>( } if cfg!(debug_assertions) { - module_id.register_debug_idents(&ident_ids); + module_id.register_debug_idents(ident_ids); } ident_ids.clone() @@ -3471,7 +3490,8 @@ fn fabricate_effects_module<'a>( let module_ids = { (*module_ids).lock().clone() }.into_module_ids(); let mut scope = roc_can::scope::Scope::new(module_id, &mut var_store); - let mut can_env = roc_can::env::Env::new(module_id, dep_idents, &module_ids, exposed_ident_ids); + let mut can_env = + roc_can::env::Env::new(module_id, &dep_idents, &module_ids, exposed_ident_ids); let effect_symbol = scope .introduce( @@ -3604,6 +3624,7 @@ fn fabricate_effects_module<'a>( var_store, constraint, ident_ids: module_output.ident_ids, + dep_idents, module_timing, }; @@ -3678,12 +3699,12 @@ where let mut var_store = VarStore::default(); let canonicalized = canonicalize_module_defs( - &arena, + arena, parsed_defs, module_id, module_ids, exposed_ident_ids, - dep_idents, + &dep_idents, aliases, exposed_imports, &exposed_symbols, @@ -3705,7 +3726,7 @@ where module_output.scope, name.as_str().into(), &module_output.ident_ids, - &parsed_defs, + parsed_defs, )), }; @@ -3731,6 +3752,7 @@ where var_store, constraint, ident_ids: module_output.ident_ids, + dep_idents, module_timing, }; @@ -3754,7 +3776,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result, Loadi let parse_start = SystemTime::now(); let source = header.parse_state.bytes; let parse_state = header.parse_state; - let parsed_defs = match module_defs().parse(&arena, parse_state) { + let parsed_defs = match module_defs().parse(arena, parse_state) { Ok((_, success, _state)) => success, Err((_, fail, _)) => { return Err(LoadingProblem::ParsingFailed(fail.into_parse_problem( @@ -4038,7 +4060,7 @@ fn add_def_to_module<'a>( pattern_vars.push(*var); } - let layout = match layout_cache.from_var( + let layout = match layout_cache.raw_from_var( mono_env.arena, annotation, mono_env.subs, @@ -4063,7 +4085,7 @@ fn add_def_to_module<'a>( procs.insert_exposed( symbol, - ProcLayout::from_layout(mono_env.arena, layout), + ProcLayout::from_raw(mono_env.arena, layout), mono_env.arena, mono_env.subs, def.annotation, @@ -4203,6 +4225,7 @@ where var_store, ident_ids, declarations, + dep_idents, unused_imports, } => Ok(run_solve( module, @@ -4212,6 +4235,7 @@ where constraint, var_store, declarations, + dep_idents, unused_imports, )), BuildPendingSpecializations { diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index fe2c1caf7e..7fbdae8ba7 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -6,7 +6,6 @@ extern crate pretty_assertions; extern crate maplit; extern crate bumpalo; -extern crate inlinable_string; extern crate roc_collections; extern crate roc_load; extern crate roc_module; @@ -17,7 +16,6 @@ mod helpers; mod test_load { use crate::helpers::fixtures_dir; use bumpalo::Bump; - use inlinable_string::InlinableString; use roc_can::builtins::builtin_defs_map; use roc_can::def::Declaration::*; use roc_can::def::Def; @@ -169,8 +167,8 @@ mod test_load { .expect("Test ModuleID not found in module_ids"); // App module names are hardcoded and not based on anything user-specified - if expected_name != ModuleName::APP { - assert_eq!(expected_name, &InlinableString::from(module_name)); + if expected_name.as_str() != ModuleName::APP { + assert_eq!(&expected_name.as_str(), &module_name); } loaded_module @@ -184,12 +182,11 @@ mod test_load { expected_types: &mut HashMap<&str, &str>, ) { for (symbol, expr_var) in &def.pattern_vars { - let content = subs.get(*expr_var).content; - name_all_type_vars(*expr_var, subs); - let actual_str = content_to_string(content, subs, home, &interns); - let fully_qualified = symbol.fully_qualified(&interns, home).to_string(); + let content = subs.get_content_without_compacting(*expr_var); + let actual_str = content_to_string(content, subs, home, interns); + let fully_qualified = symbol.fully_qualified(interns, home).to_string(); let expected_type = expected_types .remove(fully_qualified.as_str()) .unwrap_or_else(|| { @@ -341,7 +338,7 @@ mod test_load { .get_name(loaded_module.module_id) .expect("Test ModuleID not found in module_ids"); - assert_eq!(expected_name, &InlinableString::from("Primary")); + assert_eq!(expected_name.as_str(), "Primary"); assert_eq!(def_count, 10); } diff --git a/compiler/module/Cargo.toml b/compiler/module/Cargo.toml index 86f548f34b..239468c8c6 100644 --- a/compiler/module/Cargo.toml +++ b/compiler/module/Cargo.toml @@ -7,10 +7,11 @@ license = "UPL-1.0" [dependencies] roc_region = { path = "../region" } +roc_ident = { path = "../ident" } roc_collections = { path = "../collections" } bumpalo = { version = "3.6.1", features = ["collections"] } -inlinable_string = "0.1" lazy_static = "1.4" +static_assertions = "1.1.0" [dev-dependencies] pretty_assertions = "0.5.1" diff --git a/compiler/module/src/ident.rs b/compiler/module/src/ident.rs index 16e38aabb6..0de36bd0bd 100644 --- a/compiler/module/src/ident.rs +++ b/compiler/module/src/ident.rs @@ -1,13 +1,13 @@ use crate::symbol::{Interns, ModuleId, Symbol}; -use inlinable_string::InlinableString; +pub use roc_ident::IdentStr; use std::fmt; /// This could be uppercase or lowercase, qualified or unqualified. #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Ident(InlinableString); +pub struct Ident(IdentStr); impl Ident { - pub fn as_inline_str(&self) -> &InlinableString { + pub fn as_inline_str(&self) -> &IdentStr { &self.0 } } @@ -18,24 +18,32 @@ pub struct QualifiedModuleName<'a> { } #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct ModuleName(InlinableString); +pub struct ModuleName(IdentStr); + +impl std::ops::Deref for ModuleName { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.0.as_str() + } +} /// An uncapitalized identifier, such as a field name or local variable #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Lowercase(InlinableString); +pub struct Lowercase(IdentStr); /// A capitalized identifier, such as a tag name or module name #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Uppercase(InlinableString); +pub struct Uppercase(IdentStr); /// A string representing a foreign (linked-in) symbol #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] -pub struct ForeignSymbol(InlinableString); +pub struct ForeignSymbol(IdentStr); #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum TagName { /// Global tags have no module, but tend to be short strings (since they're - /// never qualified), so we store them as inlinable strings. + /// never qualified), so we store them as ident strings. /// /// This is allows canonicalization to happen in parallel without locks. /// If global tags had a Symbol representation, then each module would have to @@ -51,12 +59,18 @@ pub enum TagName { Closure(Symbol), } +static_assertions::assert_eq_size!([u8; 24], TagName); + impl TagName { - pub fn as_string(&self, interns: &Interns, home: ModuleId) -> InlinableString { + pub fn as_ident_str(&self, interns: &Interns, home: ModuleId) -> IdentStr { match self { - TagName::Global(uppercase) => uppercase.as_inline_str().clone(), - TagName::Private(symbol) => symbol.fully_qualified(interns, home), - TagName::Closure(symbol) => symbol.fully_qualified(interns, home), + TagName::Global(uppercase) => uppercase.as_ident_str().clone(), + TagName::Private(symbol) => { + symbol.fully_qualified(interns, home).as_ident_str().clone() + } + TagName::Closure(symbol) => { + symbol.fully_qualified(interns, home).as_ident_str().clone() + } } } } @@ -74,10 +88,10 @@ impl ModuleName { pub const RESULT: &'static str = "Result"; pub fn as_str(&self) -> &str { - &*self.0 + self.0.as_str() } - pub fn as_inline_str(&self) -> &InlinableString { + pub fn as_ident_str(&self) -> &IdentStr { &self.0 } @@ -99,6 +113,12 @@ impl<'a> From<&'a str> for ModuleName { } } +impl<'a> From for ModuleName { + fn from(string: IdentStr) -> Self { + Self(string.as_str().into()) + } +} + impl From> for ModuleName { fn from(string: Box) -> Self { Self((string.as_ref()).into()) @@ -111,36 +131,24 @@ impl From for ModuleName { } } -impl From for ModuleName { - fn from(string: InlinableString) -> Self { - Self(string) - } -} - -impl From for InlinableString { - fn from(name: ModuleName) -> Self { - name.0 - } -} - -impl<'a> From<&'a ModuleName> for &'a InlinableString { - fn from(name: &'a ModuleName) -> Self { - &name.0 - } -} - impl From for Box { fn from(name: ModuleName) -> Self { name.0.to_string().into() } } +impl fmt::Display for ModuleName { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + impl ForeignSymbol { pub fn as_str(&self) -> &str { - &*self.0 + self.0.as_str() } - pub fn as_inline_str(&self) -> &InlinableString { + pub fn as_inline_str(&self) -> &IdentStr { &self.0 } } @@ -159,10 +167,10 @@ impl<'a> From for ForeignSymbol { impl Uppercase { pub fn as_str(&self) -> &str { - &*self.0 + self.0.as_str() } - pub fn as_inline_str(&self) -> &InlinableString { + pub fn as_ident_str(&self) -> &IdentStr { &self.0 } } @@ -181,7 +189,7 @@ impl<'a> From for Uppercase { impl Lowercase { pub fn as_str(&self) -> &str { - &*self.0 + self.0.as_str() } } @@ -197,16 +205,10 @@ impl<'a> From for Lowercase { } } -impl From for InlinableString { - fn from(lowercase: Lowercase) -> Self { - lowercase.0 - } -} - impl AsRef for Ident { #[inline(always)] fn as_ref(&self) -> &str { - self.0.as_ref() + self.0.as_str() } } @@ -228,19 +230,19 @@ impl From for Ident { } } -impl From for Ident { - fn from(string: InlinableString) -> Self { +impl From for Ident { + fn from(string: IdentStr) -> Self { Self(string) } } -impl From for InlinableString { +impl From for IdentStr { fn from(ident: Ident) -> Self { ident.0 } } -impl<'a> From<&'a Ident> for &'a InlinableString { +impl<'a> From<&'a Ident> for &'a IdentStr { fn from(ident: &'a Ident) -> Self { &ident.0 } @@ -252,6 +254,12 @@ impl From for Box { } } +impl fmt::Display for Ident { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + /// Rather than displaying as this: /// /// Lowercase("foo") diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 98a1f48cf8..583e6017c1 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -7,13 +7,14 @@ pub enum LowLevel { StrJoinWith, StrIsEmpty, StrStartsWith, - StrStartsWithCodePoint, + StrStartsWithCodePt, StrEndsWith, StrSplit, StrCountGraphemes, StrFromInt, StrFromUtf8, - StrToBytes, + StrFromUtf8Range, + StrToUtf8, StrFromFloat, ListLen, ListGetUnsafe, @@ -86,6 +87,8 @@ pub enum LowLevel { NumAtan, NumAcos, NumAsin, + NumBytesToU16, + NumBytesToU32, NumBitwiseAnd, NumBitwiseXor, NumBitwiseOr, @@ -109,91 +112,21 @@ impl LowLevel { use LowLevel::*; match self { - StrConcat - | StrJoinWith - | StrIsEmpty - | StrStartsWith - | StrStartsWithCodePoint - | StrEndsWith - | StrSplit - | StrCountGraphemes - | StrFromInt - | StrFromUtf8 - | StrToBytes - | StrFromFloat - | ListLen - | ListGetUnsafe - | ListSet - | ListDrop - | ListSingle - | ListRepeat - | ListReverse - | ListConcat - | ListContains - | ListAppend - | ListPrepend - | ListJoin - | ListRange - | ListSwap - | DictSize - | DictEmpty - | DictInsert - | DictRemove - | DictContains - | DictGetUnsafe - | DictKeys - | DictValues - | DictUnion - | DictIntersection - | DictDifference - | SetFromList - | NumAdd - | NumAddWrap - | NumAddChecked - | NumSub - | NumSubWrap - | NumSubChecked - | NumMul - | NumMulWrap - | NumMulChecked - | NumGt - | NumGte - | NumLt - | NumLte - | NumCompare - | NumDivUnchecked - | NumRemUnchecked - | NumIsMultipleOf - | NumAbs - | NumNeg - | NumSin - | NumCos - | NumSqrtUnchecked - | NumLogUnchecked - | NumRound - | NumToFloat - | NumPow - | NumCeiling - | NumPowInt - | NumFloor - | NumIsFinite - | NumAtan - | NumAcos - | NumAsin - | NumBitwiseAnd - | NumBitwiseXor - | NumBitwiseOr - | NumShiftLeftBy - | NumShiftRightBy - | NumShiftRightZfBy - | NumIntCast - | Eq - | NotEq - | And - | Or - | Not - | Hash - | ExpectTrue => false, + StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt + | StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 + | StrFromUtf8Range | StrToUtf8 | StrFromFloat | ListLen | ListGetUnsafe | ListSet + | ListDrop | ListSingle | ListRepeat | ListReverse | ListConcat | ListContains + | ListAppend | ListPrepend | ListJoin | ListRange | ListSwap | DictSize | DictEmpty + | DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues + | DictUnion | DictIntersection | DictDifference | SetFromList | NumAdd | NumAddWrap + | NumAddChecked | NumSub | NumSubWrap | NumSubChecked | NumMul | NumMulWrap + | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare | NumDivUnchecked + | NumRemUnchecked | NumIsMultipleOf | NumAbs | NumNeg | NumSin | NumCos + | NumSqrtUnchecked | NumLogUnchecked | NumRound | NumToFloat | NumPow | NumCeiling + | NumPowInt | NumFloor | NumIsFinite | NumAtan | NumAcos | NumAsin | NumBitwiseAnd + | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumBytesToU16 + | NumBytesToU32 | NumShiftRightZfBy | NumIntCast | Eq | NotEq | And | Or | Not + | Hash | ExpectTrue => false, ListMap | ListMap2 | ListMap3 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 890c746020..01e829c39c 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -1,6 +1,6 @@ -use crate::ident::Ident; -use inlinable_string::InlinableString; +use crate::ident::{Ident, ModuleName}; use roc_collections::all::{default_hasher, MutMap, SendMap}; +use roc_ident::IdentStr; use roc_region::all::Region; use std::collections::HashMap; use std::{fmt, u32}; @@ -52,7 +52,7 @@ impl Symbol { self.module_id().is_builtin() } - pub fn module_string<'a>(&self, interns: &'a Interns) -> &'a InlinableString { + pub fn module_string<'a>(&self, interns: &'a Interns) -> &'a ModuleName { interns .module_ids .get_name(self.module_id()) @@ -65,7 +65,11 @@ impl Symbol { }) } - pub fn ident_string(self, interns: &Interns) -> &InlinableString { + pub fn as_str(self, interns: &Interns) -> &str { + self.ident_str(interns).as_str() + } + + pub fn ident_str(self, interns: &Interns) -> &IdentStr { let ident_ids = interns .all_ident_ids .get(&self.module_id()) @@ -77,30 +81,33 @@ impl Symbol { ) }); - ident_ids.get_name(self.ident_id()).unwrap_or_else(|| { - panic!( - "ident_string's IdentIds did not contain an entry for {} in module {:?}", - self.ident_id().0, - self.module_id() - ) - }) + ident_ids + .get_name(self.ident_id()) + .unwrap_or_else(|| { + panic!( + "ident_string's IdentIds did not contain an entry for {} in module {:?}", + self.ident_id().0, + self.module_id() + ) + }) + .into() } pub fn as_u64(self) -> u64 { self.0 } - pub fn fully_qualified(self, interns: &Interns, home: ModuleId) -> InlinableString { + pub fn fully_qualified(self, interns: &Interns, home: ModuleId) -> ModuleName { let module_id = self.module_id(); if module_id == home { - self.ident_string(interns).clone() + self.ident_str(interns).clone().into() } else { // TODO do this without format! to avoid allocation for short strings format!( "{}.{}", - self.module_string(interns), - self.ident_string(interns) + self.module_string(interns).as_str(), + self.ident_str(interns) ) .into() } @@ -209,11 +216,11 @@ pub struct Interns { } impl Interns { - pub fn module_id(&mut self, name: &InlinableString) -> ModuleId { + pub fn module_id(&mut self, name: &ModuleName) -> ModuleId { self.module_ids.get_or_insert(name) } - pub fn module_name(&self, module_id: ModuleId) -> &InlinableString { + pub fn module_name(&self, module_id: ModuleId) -> &ModuleName { self.module_ids.get_name(module_id).unwrap_or_else(|| { panic!( "Unable to find interns entry for module_id {:?} in Interns {:?}", @@ -222,7 +229,9 @@ impl Interns { }) } - pub fn symbol(&self, module_id: ModuleId, ident: InlinableString) -> Symbol { + pub fn symbol(&self, module_id: ModuleId, ident: IdentStr) -> Symbol { + let ident: Ident = ident.into(); + match self.all_ident_ids.get(&module_id) { Some(ident_ids) => match ident_ids.get_id(&ident) { Some(ident_id) => Symbol::new(module_id, *ident_id), @@ -278,7 +287,7 @@ impl ModuleId { // This is a no-op that should get DCE'd } - pub fn to_string(self, interns: &Interns) -> &InlinableString { + pub fn to_ident_str(self, interns: &Interns) -> &ModuleName { interns .module_ids .get_name(self) @@ -317,7 +326,7 @@ impl fmt::Debug for ModuleId { } } - /// In relese builds, all we have access to is the number, so only display that. + /// In release builds, all we have access to is the number, so only display that. #[cfg(not(debug_assertions))] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) @@ -338,7 +347,7 @@ pub enum PackageQualified<'a, T> { } /// Package-qualified module name -pub type PQModuleName<'a> = PackageQualified<'a, InlinableString>; +pub type PQModuleName<'a> = PackageQualified<'a, ModuleName>; impl<'a, T> PackageQualified<'a, T> { pub fn as_inner(&self) -> &T { @@ -368,7 +377,7 @@ impl<'a> PackageModuleIds<'a> { self.by_name.insert(module_name.clone(), module_id); if cfg!(debug_assertions) { - Self::insert_debug_name(module_id, &module_name); + Self::insert_debug_name(module_id, module_name); } module_id @@ -377,13 +386,13 @@ impl<'a> PackageModuleIds<'a> { } pub fn into_module_ids(self) -> ModuleIds { - let by_name: MutMap = self + let by_name: MutMap = self .by_name .into_iter() .map(|(pqname, module_id)| (pqname.as_inner().clone(), module_id)) .collect(); - let by_id: Vec = self + let by_id: Vec = self .by_id .into_iter() .map(|pqname| pqname.as_inner().clone()) @@ -399,9 +408,9 @@ impl<'a> PackageModuleIds<'a> { names .entry(module_id.0) .or_insert_with(|| match module_name { - PQModuleName::Unqualified(module) => module.to_string().into(), + PQModuleName::Unqualified(module) => module.as_str().into(), PQModuleName::Qualified(package, module) => { - let name = format!("{}.{}", package, module).into(); + let name = format!("{}.{}", package, module.as_str()).into(); name } }); @@ -431,13 +440,13 @@ impl<'a> PackageModuleIds<'a> { /// Since these are interned strings, this shouldn't result in many total allocations in practice. #[derive(Debug, Clone)] pub struct ModuleIds { - by_name: MutMap, + by_name: MutMap, /// Each ModuleId is an index into this Vec - by_id: Vec, + by_id: Vec, } impl ModuleIds { - pub fn get_or_insert(&mut self, module_name: &InlinableString) -> ModuleId { + pub fn get_or_insert(&mut self, module_name: &ModuleName) -> ModuleId { match self.by_name.get(module_name) { Some(id) => *id, None => { @@ -449,7 +458,7 @@ impl ModuleIds { self.by_name.insert(module_name.clone(), module_id); if cfg!(debug_assertions) { - Self::insert_debug_name(module_id, &module_name); + Self::insert_debug_name(module_id, module_name); } module_id @@ -458,29 +467,29 @@ impl ModuleIds { } #[cfg(debug_assertions)] - fn insert_debug_name(module_id: ModuleId, module_name: &InlinableString) { + fn insert_debug_name(module_id: ModuleId, module_name: &ModuleName) { let mut names = DEBUG_MODULE_ID_NAMES.lock().expect("Failed to acquire lock for Debug interning into DEBUG_MODULE_ID_NAMES, presumably because a thread panicked."); // TODO make sure modules are never added more than once! names .entry(module_id.0) - .or_insert_with(|| module_name.to_string().into()); + .or_insert_with(|| module_name.as_str().to_string().into()); } #[cfg(not(debug_assertions))] - fn insert_debug_name(_module_id: ModuleId, _module_name: &InlinableString) { + fn insert_debug_name(_module_id: ModuleId, _module_name: &ModuleName) { // By design, this is a no-op in release builds! } - pub fn get_id(&self, module_name: &InlinableString) -> Option<&ModuleId> { + pub fn get_id(&self, module_name: &ModuleName) -> Option<&ModuleId> { self.by_name.get(module_name) } - pub fn get_name(&self, id: ModuleId) -> Option<&InlinableString> { + pub fn get_name(&self, id: ModuleId) -> Option<&ModuleName> { self.by_id.get(id.0 as usize) } - pub fn available_modules(&self) -> impl Iterator { + pub fn available_modules(&self) -> impl Iterator { self.by_id.iter() } } @@ -500,23 +509,23 @@ pub struct IdentId(u32); /// Since these are interned strings, this shouldn't result in many total allocations in practice. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct IdentIds { - by_ident: MutMap, + by_ident: MutMap, /// Each IdentId is an index into this Vec - by_id: Vec, + by_id: Vec, next_generated_name: u32, } impl IdentIds { - pub fn idents(&self) -> impl Iterator { + pub fn idents(&self) -> impl Iterator { self.by_id .iter() .enumerate() .map(|(index, ident)| (IdentId(index as u32), ident)) } - pub fn add(&mut self, ident_name: InlinableString) -> IdentId { + pub fn add(&mut self, ident_name: Ident) -> IdentId { let by_id = &mut self.by_id; let ident_id = IdentId(by_id.len() as u32); @@ -526,7 +535,7 @@ impl IdentIds { ident_id } - pub fn get_or_insert(&mut self, name: &InlinableString) -> IdentId { + pub fn get_or_insert(&mut self, name: &Ident) -> IdentId { match self.by_ident.get(name) { Some(id) => *id, None => { @@ -592,7 +601,7 @@ impl IdentIds { /// This is used, for example, during canonicalization of an Expr::Closure /// to generate a unique symbol to refer to that closure. pub fn gen_unique(&mut self) -> IdentId { - // TODO convert this directly from u32 into InlinableString, + // TODO convert this directly from u32 into IdentStr, // without allocating an extra string along the way like this. let ident = self.next_generated_name.to_string().into(); @@ -601,11 +610,11 @@ impl IdentIds { self.add(ident) } - pub fn get_id(&self, ident_name: &InlinableString) -> Option<&IdentId> { + pub fn get_id(&self, ident_name: &Ident) -> Option<&IdentId> { self.by_ident.get(ident_name) } - pub fn get_name(&self, id: IdentId) -> Option<&InlinableString> { + pub fn get_name(&self, id: IdentId) -> Option<&Ident> { self.by_id.get(id.0 as usize) } } @@ -636,13 +645,16 @@ macro_rules! define_builtins { $ident_name.into(), )+ ]; - let mut by_ident = MutMap::default(); + let mut by_ident = MutMap::with_capacity_and_hasher(by_id.len(), default_hasher()); $( - debug_assert!(!by_ident.contains_key($ident_name.clone().into()), "Error setting up Builtins: when inserting {} …: {:?} into module {} …: {:?} - the Ident name {:?} is already present in the map. Check the map for duplicate ident names within the {:?} module!", $ident_id, $ident_name, $module_id, $module_name, $ident_name, $module_name); debug_assert!(by_ident.len() == $ident_id, "Error setting up Builtins: when inserting {} …: {:?} into module {} …: {:?} - this entry was assigned an ID of {}, but based on insertion order, it should have had an ID of {} instead! To fix this, change it from {} …: {:?} to {} …: {:?} instead.", $ident_id, $ident_name, $module_id, $module_name, $ident_id, by_ident.len(), $ident_id, $ident_name, by_ident.len(), $ident_name); - by_ident.insert($ident_name.into(), IdentId($ident_id)); + let exists = by_ident.insert($ident_name.into(), IdentId($ident_id)); + + if let Some(_) = exists { + debug_assert!(false, "Error setting up Builtins: when inserting {} …: {:?} into module {} …: {:?} - the Ident name {:?} is already present in the map. Check the map for duplicate ident names within the {:?} module!", $ident_id, $ident_name, $module_id, $module_name, $ident_name, $module_name); + } )+ IdentIds { @@ -694,7 +706,7 @@ macro_rules! define_builtins { let mut by_id = Vec::with_capacity(capacity); let mut insert_both = |id: ModuleId, name_str: &'static str| { - let name: InlinableString = name_str.into(); + let name: ModuleName = name_str.into(); if cfg!(debug_assertions) { Self::insert_debug_name(id, &name); @@ -721,8 +733,8 @@ macro_rules! define_builtins { let mut by_id = Vec::with_capacity(capacity); let mut insert_both = |id: ModuleId, name_str: &'static str| { - let raw_name: InlinableString = name_str.into(); - let name = PQModuleName::Unqualified(raw_name); + let raw_name: IdentStr = name_str.into(); + let name = PQModuleName::Unqualified(raw_name.into()); if cfg!(debug_assertions) { Self::insert_debug_name(id, &name); @@ -813,6 +825,9 @@ define_builtins! { // a caller (wrapper) for comparison 21 GENERIC_COMPARE_REF: "#generic_compare_ref" + + // used to initialize parameters in borrow.rs + 22 EMPTY_PARAM: "#empty_param" } 1 NUM: "Num" => { 0 NUM_NUM: "Num" imported // the Num.Num type alias @@ -915,7 +930,12 @@ define_builtins! { 97 NUM_INT_CAST: "intCast" 98 NUM_MAX_I128: "maxI128" 99 NUM_IS_MULTIPLE_OF: "isMultipleOf" - + 100 NUM_AT_DECIMAL: "@Decimal" + 101 NUM_DECIMAL: "Decimal" imported + 102 NUM_DEC: "Dec" imported // the Num.Dectype alias + 103 NUM_BYTES_TO_U16: "bytesToU16" + 104 NUM_BYTES_TO_U32: "bytesToU32" + 105 NUM_CAST_TO_NAT: "#castToNat" } 2 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias @@ -942,9 +962,10 @@ define_builtins! { 12 STR_FROM_UTF8: "fromUtf8" 13 STR_UT8_PROBLEM: "Utf8Problem" // the Utf8Problem type alias 14 STR_UT8_BYTE_PROBLEM: "Utf8ByteProblem" // the Utf8ByteProblem type alias - 15 STR_TO_BYTES: "toBytes" - 16 STR_STARTS_WITH_CODE_POINT: "startsWithCodePoint" + 15 STR_TO_UTF8: "toUtf8" + 16 STR_STARTS_WITH_CODE_PT: "startsWithCodePt" 17 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime + 18 STR_FROM_UTF8_RANGE: "fromUtf8Range" } 4 LIST: "List" => { 0 LIST_LIST: "List" imported // the List.List type alias @@ -1012,8 +1033,6 @@ define_builtins! { 14 DICT_UNION: "union" 15 DICT_INTERSECTION: "intersection" 16 DICT_DIFFERENCE: "difference" - - } 7 SET: "Set" => { 0 SET_SET: "Set" imported // the Set.Set type alias diff --git a/compiler/mono/Cargo.toml b/compiler/mono/Cargo.toml index cd0e0e6d77..9bd2127016 100644 --- a/compiler/mono/Cargo.toml +++ b/compiler/mono/Cargo.toml @@ -19,6 +19,7 @@ morphic_lib = { path = "../../vendor/morphic_lib" } bumpalo = { version = "3.6.1", features = ["collections"] } hashbrown = { version = "0.11.2", features = [ "bumpalo" ] } ven_ena = { path = "../../vendor/ena" } +ven_graph = { path = "../../vendor/pathfinding" } linked-hash-map = "0.5.4" [dev-dependencies] diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index 2b4354d26e..8d62b2b4ab 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -1,10 +1,10 @@ use morphic_lib::TypeContext; use morphic_lib::{ BlockExpr, BlockId, CalleeSpecVar, ConstDefBuilder, ConstName, EntryPointName, ExprContext, - FuncDef, FuncDefBuilder, FuncName, ModDefBuilder, ModName, ProgramBuilder, Result, TypeId, - UpdateModeVar, ValueId, + FuncDef, FuncDefBuilder, FuncName, ModDefBuilder, ModName, ProgramBuilder, Result, + TypeDefBuilder, TypeId, TypeName, UpdateModeVar, ValueId, }; -use roc_collections::all::MutMap; +use roc_collections::all::{MutMap, MutSet}; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; use std::convert::TryFrom; @@ -26,6 +26,26 @@ pub fn func_name_bytes(proc: &Proc) -> [u8; SIZE] { const DEBUG: bool = false; const SIZE: usize = if DEBUG { 50 } else { 16 }; +#[derive(Debug, Clone, Copy, Hash)] +struct TagUnionId(u64); + +fn recursive_tag_union_name_bytes(union_layout: &UnionLayout) -> TagUnionId { + use std::collections::hash_map::DefaultHasher; + use std::hash::Hash; + use std::hash::Hasher; + + let mut hasher = DefaultHasher::new(); + union_layout.hash(&mut hasher); + + TagUnionId(hasher.finish()) +} + +impl TagUnionId { + const fn as_bytes(&self) -> [u8; 8] { + self.0.to_ne_bytes() + } +} + pub fn func_name_bytes_help<'a, I>( symbol: Symbol, argument_layouts: I, @@ -44,24 +64,10 @@ where let mut hasher = DefaultHasher::new(); for layout in argument_layouts { - match layout { - Layout::Closure(_, lambda_set, _) => { - lambda_set.runtime_representation().hash(&mut hasher); - } - _ => { - layout.hash(&mut hasher); - } - } + layout.hash(&mut hasher); } - match return_layout { - Layout::Closure(_, lambda_set, _) => { - lambda_set.runtime_representation().hash(&mut hasher); - } - _ => { - return_layout.hash(&mut hasher); - } - } + return_layout.hash(&mut hasher); hasher.finish() }; @@ -134,6 +140,8 @@ where let entry_point_name = FuncName(ENTRY_POINT_NAME); m.add_func(entry_point_name, entry_point_function)?; + let mut type_definitions = MutSet::default(); + // all other functions for proc in procs { let bytes = func_name_bytes(proc); @@ -148,11 +156,32 @@ where ); } - let spec = proc_spec(proc)?; + let (spec, type_names) = proc_spec(proc)?; + + type_definitions.extend(type_names); m.add_func(func_name, spec)?; } + for union_layout in type_definitions { + let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes(); + let type_name = TypeName(&type_name_bytes); + + let mut builder = TypeDefBuilder::new(); + + let variant_types = build_variant_types(&mut builder, &union_layout)?; + let root_type = if let UnionLayout::NonNullableUnwrapped(_) = union_layout { + debug_assert_eq!(variant_types.len(), 1); + variant_types[0] + } else { + builder.add_union_type(&variant_types)? + }; + + let type_def = builder.build(root_type)?; + + m.add_named_type(type_name, type_def)?; + } + m.build()? }; @@ -195,7 +224,7 @@ fn build_entry_point(layout: crate::ir::ProcLayout, func_name: FuncName) -> Resu Ok(spec) } -fn proc_spec(proc: &Proc) -> Result { +fn proc_spec<'a>(proc: &Proc<'a>) -> Result<(FuncDef, MutSet>)> { let mut builder = FuncDefBuilder::new(); let mut env = Env::default(); @@ -218,21 +247,22 @@ fn proc_spec(proc: &Proc) -> Result { let spec = builder.build(arg_type_id, ret_type_id, root)?; - Ok(spec) + Ok((spec, env.type_names)) } #[derive(Default)] -struct Env { +struct Env<'a> { symbols: MutMap, join_points: MutMap, + type_names: MutSet>, } -fn stmt_spec( +fn stmt_spec<'a>( builder: &mut FuncDefBuilder, - env: &mut Env, + env: &mut Env<'a>, block: BlockId, layout: &Layout, - stmt: &Stmt, + stmt: &Stmt<'a>, ) -> Result { use Stmt::*; @@ -259,30 +289,6 @@ fn stmt_spec( Ok(result) } - Invoke { - symbol, - call, - layout: call_layout, - pass, - fail, - exception_id: _, - } => { - // a call that might throw an exception - - let value_id = call_spec(builder, env, block, call_layout, call)?; - - let pass_block = builder.add_block(); - env.symbols.insert(*symbol, value_id); - let pass_value_id = stmt_spec(builder, env, pass_block, layout, pass)?; - env.symbols.remove(symbol); - let pass_block_expr = BlockExpr(pass_block, pass_value_id); - - let fail_block = builder.add_block(); - let fail_value_id = stmt_spec(builder, env, fail_block, layout, fail)?; - let fail_block_expr = BlockExpr(fail_block, fail_value_id); - - builder.add_choice(block, &[pass_block_expr, fail_block_expr]) - } Switch { cond_symbol: _, cond_layout: _, @@ -390,7 +396,7 @@ fn stmt_spec( let jpid = env.join_points[id]; builder.add_jump(block, jpid, argument, ret_type_id) } - Resume(_) | RuntimeError(_) => { + RuntimeError(_) => { let type_id = layout_spec(builder, layout)?; builder.add_terminate(block, type_id) @@ -420,7 +426,27 @@ fn build_tuple_value( builder.add_make_tuple(block, &value_ids) } -fn build_tuple_type(builder: &mut FuncDefBuilder, layouts: &[Layout]) -> Result { +#[derive(Clone, Debug, PartialEq)] +enum WhenRecursive<'a> { + Unreachable, + Loop(UnionLayout<'a>), +} + +fn build_recursive_tuple_type( + builder: &mut impl TypeContext, + layouts: &[Layout], + when_recursive: &WhenRecursive, +) -> Result { + let mut field_types = Vec::new(); + + for field in layouts.iter() { + field_types.push(layout_spec_help(builder, field, when_recursive)?); + } + + builder.add_tuple_type(&field_types) +} + +fn build_tuple_type(builder: &mut impl TypeContext, layouts: &[Layout]) -> Result { let mut field_types = Vec::new(); for field in layouts.iter() { @@ -739,15 +765,20 @@ fn lowlevel_spec( builder.add_sub_block(block, sub_block) } + NumToFloat => { + // just dream up a unit value + builder.add_make_tuple(block, &[]) + } Eq | NotEq => { // just dream up a unit value builder.add_make_tuple(block, &[]) } - NumLte | NumLt | NumGt | NumGte => { + NumLte | NumLt | NumGt | NumGte | NumCompare => { // just dream up a unit value builder.add_make_tuple(block, &[]) } - ListLen => { + ListLen | DictSize => { + // TODO should this touch the heap cell? // just dream up a unit value builder.add_make_tuple(block, &[]) } @@ -773,9 +804,86 @@ fn lowlevel_spec( builder.add_bag_insert(block, bag, to_insert)?; - Ok(list) + let new_cell = builder.add_new_heap_cell(block)?; + builder.add_make_tuple(block, &[new_cell, bag]) + } + ListSwap => { + let list = env.symbols[&arguments[0]]; + + 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_cell = builder.add_new_heap_cell(block)?; + builder.add_make_tuple(block, &[new_cell, bag]) + } + ListAppend => { + 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]) + } + DictEmpty => { + match layout { + Layout::Builtin(Builtin::EmptyDict) => { + // just make up an element type + let type_id = builder.add_tuple_type(&[])?; + new_dict(builder, block, type_id, type_id) + } + Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => { + let key_id = layout_spec(builder, key_layout)?; + let value_id = layout_spec(builder, value_layout)?; + new_dict(builder, block, key_id, value_id) + } + _ => unreachable!("empty array does not have a list layout"), + } + } + DictGetUnsafe => { + // NOTE DictGetUnsafe returns a { flag: Bool, value: v } + // when the flag is True, the value is found and defined; + // otherwise it is not and `Dict.get` should return `Err ...` + + let dict = env.symbols[&arguments[0]]; + let key = env.symbols[&arguments[1]]; + + // indicate that we use the key + builder.add_recursive_touch(block, key)?; + + 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 _unit = builder.add_touch(block, cell)?; + builder.add_bag_get(block, bag) + } + DictInsert => { + let dict = env.symbols[&arguments[0]]; + let key = env.symbols[&arguments[1]]; + let value = env.symbols[&arguments[2]]; + + let key_value = builder.add_make_tuple(block, &[key, value])?; + + 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 _unit = builder.add_update(block, update_mode_var, cell)?; + + builder.add_bag_insert(block, bag, key_value)?; + + let new_cell = builder.add_new_heap_cell(block)?; + builder.add_make_tuple(block, &[new_cell, bag]) } _other => { + // println!("missing {:?}", _other); // TODO overly pessimstic let arguments: Vec<_> = arguments.iter().map(|symbol| env.symbols[symbol]).collect(); @@ -786,73 +894,161 @@ fn lowlevel_spec( } } +fn recursive_tag_variant( + builder: &mut impl TypeContext, + union_layout: &UnionLayout, + fields: &[Layout], +) -> Result { + let when_recursive = WhenRecursive::Loop(*union_layout); + + let data_id = build_recursive_tuple_type(builder, fields, &when_recursive)?; + let cell_id = builder.add_heap_cell_type(); + + builder.add_tuple_type(&[cell_id, data_id]) +} + fn build_variant_types( - builder: &mut FuncDefBuilder, + builder: &mut impl TypeContext, union_layout: &UnionLayout, ) -> Result> { use UnionLayout::*; - let mut result = Vec::new(); + let mut result; match union_layout { NonRecursive(tags) => { + result = Vec::with_capacity(tags.len()); + for tag in tags.iter() { result.push(build_tuple_type(builder, tag)?); } } - Recursive(_) => unreachable!(), - NonNullableUnwrapped(_) => unreachable!(), + Recursive(tags) => { + result = Vec::with_capacity(tags.len()); + + for tag in tags.iter() { + result.push(recursive_tag_variant(builder, union_layout, tag)?); + } + } + NonNullableUnwrapped(fields) => { + result = vec![recursive_tag_variant(builder, union_layout, fields)?]; + } NullableWrapped { - nullable_id: _, - other_tags: _, - } => unreachable!(), + nullable_id, + other_tags: tags, + } => { + result = Vec::with_capacity(tags.len() + 1); + + let cutoff = *nullable_id as usize; + + for tag in tags[..cutoff].iter() { + result.push(recursive_tag_variant(builder, union_layout, tag)?); + } + + let unit = builder.add_tuple_type(&[])?; + result.push(unit); + + for tag in tags[cutoff..].iter() { + result.push(recursive_tag_variant(builder, union_layout, tag)?); + } + } NullableUnwrapped { - nullable_id: _, - other_fields: _, - } => unreachable!(), + nullable_id, + other_fields: fields, + } => { + let unit = builder.add_tuple_type(&[])?; + let other_type = recursive_tag_variant(builder, union_layout, fields)?; + + if *nullable_id { + // nullable_id == 1 + result = vec![other_type, unit]; + } else { + result = vec![unit, other_type]; + } + } } Ok(result) } +#[allow(dead_code)] fn worst_case_type(context: &mut impl TypeContext) -> Result { let cell = context.add_heap_cell_type(); context.add_bag_type(cell) } -fn expr_spec( +fn expr_spec<'a>( builder: &mut FuncDefBuilder, - env: &mut Env, + env: &mut Env<'a>, block: BlockId, - layout: &Layout, - expr: &Expr, + layout: &Layout<'a>, + expr: &Expr<'a>, ) -> Result { use Expr::*; match expr { Literal(literal) => literal_spec(builder, block, literal), Call(call) => call_spec(builder, env, block, layout, call), - Tag { + Reuse { tag_layout, tag_name: _, tag_id, - union_size: _, arguments, - } => match tag_layout { - UnionLayout::NonRecursive(_) => { - let value_id = build_tuple_value(builder, env, block, arguments)?; - let variant_types = build_variant_types(builder, tag_layout)?; - builder.add_make_union(block, &variant_types, *tag_id as u32, value_id) - } - UnionLayout::Recursive(_) - | UnionLayout::NonNullableUnwrapped(_) - | UnionLayout::NullableWrapped { .. } - | UnionLayout::NullableUnwrapped { .. } => { - let result_type = worst_case_type(builder)?; - let value_id = build_tuple_value(builder, env, block, arguments)?; - builder.add_unknown_with(block, &[value_id], result_type) - } - }, + .. + } + | Tag { + tag_layout, + tag_name: _, + tag_id, + arguments, + } => { + let variant_types = build_variant_types(builder, tag_layout)?; + + let data_id = build_tuple_value(builder, env, block, arguments)?; + let cell_id = builder.add_new_heap_cell(block)?; + + let value_id = match tag_layout { + UnionLayout::NonRecursive(_) => { + let value_id = build_tuple_value(builder, env, block, arguments)?; + return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id); + } + UnionLayout::NonNullableUnwrapped(_) => { + let value_id = builder.add_make_tuple(block, &[cell_id, data_id])?; + + let type_name_bytes = recursive_tag_union_name_bytes(tag_layout).as_bytes(); + let type_name = TypeName(&type_name_bytes); + + env.type_names.insert(*tag_layout); + + return builder.add_make_named(block, MOD_APP, type_name, value_id); + } + UnionLayout::Recursive(_) => builder.add_make_tuple(block, &[cell_id, data_id])?, + UnionLayout::NullableWrapped { nullable_id, .. } => { + if *tag_id == *nullable_id as u8 { + data_id + } else { + builder.add_make_tuple(block, &[cell_id, data_id])? + } + } + UnionLayout::NullableUnwrapped { nullable_id, .. } => { + if *tag_id == *nullable_id as u8 { + data_id + } else { + builder.add_make_tuple(block, &[cell_id, data_id])? + } + } + }; + + let union_id = + builder.add_make_union(block, &variant_types, *tag_id as u32, value_id)?; + + let type_name_bytes = recursive_tag_union_name_bytes(tag_layout).as_bytes(); + let type_name = TypeName(&type_name_bytes); + + env.type_names.insert(*tag_layout); + + builder.add_make_named(block, MOD_APP, type_name, union_id) + } Struct(fields) => build_tuple_value(builder, env, block, fields), UnionAtIndex { index, @@ -868,11 +1064,45 @@ fn expr_spec( builder.add_get_tuple_field(block, tuple_value_id, index) } - _ => { - // for the moment recursive tag unions don't quite work - let value_id = env.symbols[structure]; - let result_type = layout_spec(builder, layout)?; - builder.add_unknown_with(block, &[value_id], result_type) + UnionLayout::Recursive(_) + | UnionLayout::NullableUnwrapped { .. } + | UnionLayout::NullableWrapped { .. } => { + let index = (*index) as u32; + let tag_value_id = env.symbols[structure]; + + let type_name_bytes = recursive_tag_union_name_bytes(union_layout).as_bytes(); + let type_name = TypeName(&type_name_bytes); + + let union_id = builder.add_unwrap_named(block, MOD_APP, type_name, tag_value_id)?; + let variant_id = builder.add_unwrap_union(block, union_id, *tag_id as u32)?; + + // we're reading from this value, so touch the heap cell + let heap_cell = builder.add_get_tuple_field(block, variant_id, 0)?; + builder.add_touch(block, heap_cell)?; + + let tuple_value_id = builder.add_get_tuple_field(block, variant_id, 1)?; + + builder.add_get_tuple_field(block, tuple_value_id, index) + } + UnionLayout::NonNullableUnwrapped { .. } => { + let index = (*index) as u32; + debug_assert!(*tag_id == 0); + + let tag_value_id = env.symbols[structure]; + + let type_name_bytes = recursive_tag_union_name_bytes(union_layout).as_bytes(); + let type_name = TypeName(&type_name_bytes); + + let variant_id = + builder.add_unwrap_named(block, MOD_APP, type_name, tag_value_id)?; + + // we're reading from this value, so touch the heap cell + let heap_cell = builder.add_get_tuple_field(block, variant_id, 0)?; + builder.add_touch(block, heap_cell)?; + + let tuple_value_id = builder.add_get_tuple_field(block, variant_id, 1)?; + + builder.add_get_tuple_field(block, tuple_value_id, index) } }, StructAtIndex { @@ -915,8 +1145,12 @@ fn expr_spec( Err(()) => unreachable!("empty array does not have a list layout"), } } - Reuse { .. } => todo!("currently unused"), - Reset(_) => todo!("currently unused"), + Reset(symbol) => { + let type_id = layout_spec(builder, layout)?; + let value_id = env.symbols[symbol]; + + builder.add_unknown_with(block, &[value_id], type_id) + } RuntimeErrorFunction(_) => { let type_id = layout_spec(builder, layout)?; @@ -939,43 +1173,70 @@ fn literal_spec( } } -fn layout_spec(builder: &mut FuncDefBuilder, layout: &Layout) -> Result { +fn layout_spec(builder: &mut impl TypeContext, layout: &Layout) -> Result { + layout_spec_help(builder, layout, &WhenRecursive::Unreachable) +} + +fn layout_spec_help( + builder: &mut impl TypeContext, + layout: &Layout, + when_recursive: &WhenRecursive, +) -> Result { use Layout::*; match layout { - Builtin(builtin) => builtin_spec(builder, builtin), - Struct(fields) => build_tuple_type(builder, fields), - Union(union_layout) => match union_layout { - UnionLayout::NonRecursive(_) => { - let variant_types = build_variant_types(builder, union_layout)?; - builder.add_union_type(&variant_types) + Builtin(builtin) => builtin_spec(builder, builtin, when_recursive), + Struct(fields) => build_recursive_tuple_type(builder, fields, when_recursive), + Union(union_layout) => { + let variant_types = build_variant_types(builder, union_layout)?; + + match union_layout { + UnionLayout::NonRecursive(_) => builder.add_union_type(&variant_types), + UnionLayout::Recursive(_) + | UnionLayout::NullableUnwrapped { .. } + | UnionLayout::NullableWrapped { .. } + | UnionLayout::NonNullableUnwrapped(_) => { + let type_name_bytes = recursive_tag_union_name_bytes(union_layout).as_bytes(); + let type_name = TypeName(&type_name_bytes); + + Ok(builder.add_named_type(MOD_APP, type_name)) + } } - UnionLayout::Recursive(_) => worst_case_type(builder), - UnionLayout::NonNullableUnwrapped(_) => worst_case_type(builder), - UnionLayout::NullableWrapped { - nullable_id: _, - other_tags: _, - } => worst_case_type(builder), - UnionLayout::NullableUnwrapped { - nullable_id: _, - other_fields: _, - } => worst_case_type(builder), + } + RecursivePointer => match when_recursive { + WhenRecursive::Unreachable => { + unreachable!() + } + WhenRecursive::Loop(union_layout) => match union_layout { + UnionLayout::NonRecursive(_) => unreachable!(), + UnionLayout::Recursive(_) + | UnionLayout::NullableUnwrapped { .. } + | UnionLayout::NullableWrapped { .. } + | UnionLayout::NonNullableUnwrapped(_) => { + let type_name_bytes = recursive_tag_union_name_bytes(union_layout).as_bytes(); + let type_name = TypeName(&type_name_bytes); + + Ok(builder.add_named_type(MOD_APP, type_name)) + } + }, }, - RecursivePointer => worst_case_type(builder), - Closure(_, lambda_set, _) => layout_spec(builder, &lambda_set.runtime_representation()), } } -fn builtin_spec(builder: &mut FuncDefBuilder, builtin: &Builtin) -> Result { +fn builtin_spec( + builder: &mut impl TypeContext, + builtin: &Builtin, + when_recursive: &WhenRecursive, +) -> Result { use Builtin::*; match builtin { Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize => builder.add_tuple_type(&[]), - Float128 | Float64 | Float32 | Float16 => builder.add_tuple_type(&[]), + Decimal | Float128 | Float64 | Float32 | Float16 => builder.add_tuple_type(&[]), Str | EmptyStr => str_type(builder), Dict(key_layout, value_layout) => { - let value_type = layout_spec(builder, value_layout)?; - let key_type = layout_spec(builder, key_layout)?; + let value_type = layout_spec_help(builder, value_layout, when_recursive)?; + let key_type = layout_spec_help(builder, key_layout, when_recursive)?; let element_type = builder.add_tuple_type(&[key_type, value_type])?; let cell = builder.add_heap_cell_type(); @@ -984,7 +1245,7 @@ fn builtin_spec(builder: &mut FuncDefBuilder, builtin: &Builtin) -> Result { let value_type = builder.add_tuple_type(&[])?; - let key_type = layout_spec(builder, key_layout)?; + let key_type = layout_spec_help(builder, key_layout, when_recursive)?; let element_type = builder.add_tuple_type(&[key_type, value_type])?; let cell = builder.add_heap_cell_type(); @@ -992,7 +1253,7 @@ fn builtin_spec(builder: &mut FuncDefBuilder, builtin: &Builtin) -> Result { - let element_type = layout_spec(builder, element_layout)?; + let element_type = layout_spec_help(builder, element_layout, when_recursive)?; let cell = builder.add_heap_cell_type(); let bag = builder.add_bag_type(element_type)?; @@ -1041,6 +1302,18 @@ fn new_list(builder: &mut FuncDefBuilder, block: BlockId, element_type: TypeId) builder.add_make_tuple(block, &[cell, bag]) } +fn new_dict( + builder: &mut FuncDefBuilder, + block: BlockId, + key_type: TypeId, + value_type: TypeId, +) -> Result { + let cell = builder.add_new_heap_cell(block)?; + let element_type = builder.add_tuple_type(&[key_type, value_type])?; + let bag = builder.add_empty_bag(block, element_type)?; + builder.add_make_tuple(block, &[cell, bag]) +} + fn new_static_string(builder: &mut FuncDefBuilder, block: BlockId) -> Result { let module = MOD_APP; diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index d63f4dad5e..419158288a 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -2,7 +2,7 @@ use crate::ir::{Expr, JoinPointId, Param, Proc, ProcLayout, Stmt}; use crate::layout::Layout; use bumpalo::collections::Vec; use bumpalo::Bump; -use roc_collections::all::{MutMap, MutSet}; +use roc_collections::all::{default_hasher, MutMap, MutSet}; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; @@ -10,18 +10,23 @@ pub const OWNED: bool = false; pub const BORROWED: bool = true; fn should_borrow_layout(layout: &Layout) -> bool { - match layout { - Layout::Closure(_, _, _) => false, - _ => layout.is_refcounted(), - } + layout.is_refcounted() } pub fn infer_borrow<'a>( arena: &'a Bump, procs: &MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) -> ParamMap<'a> { - let mut param_map = ParamMap { - items: MutMap::default(), + // intern the layouts + + let mut param_map = { + let (declaration_to_index, total_number_of_params) = DeclarationToIndex::new(arena, procs); + + ParamMap { + declaration_to_index, + join_points: MutMap::default(), + declarations: bumpalo::vec![in arena; Param::EMPTY; total_number_of_params], + } }; for (key, proc) in procs { @@ -33,82 +38,235 @@ pub fn infer_borrow<'a>( param_set: MutSet::default(), owned: MutMap::default(), modified: false, - param_map, arena, }; - // This is a fixed-point analysis - // - // all functions initiall own all their parameters - // through a series of checks and heuristics, some arguments are set to borrowed - // when that doesn't lead to conflicts the change is kept, otherwise it may be reverted - // - // when the signatures no longer change, the analysis stops and returns the signatures - loop { - // sort the symbols (roughly) in definition order. - // TODO in the future I think we need to do this properly, and group - // mutually recursive functions (or just make all their arguments owned) + // next we first partition the functions into strongly connected components, then do a + // topological sort on these components, finally run the fix-point borrow analysis on each + // component (in top-sorted order, from primitives (std-lib) to main) - for (key, proc) in procs { - env.collect_proc(proc, key.1); - } + let successor_map = &make_successor_mapping(arena, procs); + let successors = move |key: &Symbol| { + let slice = match successor_map.get(key) { + None => &[] as &[_], + Some(s) => s.as_slice(), + }; - if !env.modified { - // if there were no modifications, we're done - break; - } else { - // otherwise see if there are changes after another iteration - env.modified = false; + slice.iter().copied() + }; + + let mut symbols = Vec::with_capacity_in(procs.len(), arena); + symbols.extend(procs.keys().map(|x| x.0)); + + let sccs = ven_graph::strongly_connected_components(&symbols, successors); + + let mut symbol_to_component = MutMap::default(); + for (i, symbols) in sccs.iter().enumerate() { + for symbol in symbols { + symbol_to_component.insert(*symbol, i); } } - env.param_map + let mut component_to_successors = Vec::with_capacity_in(sccs.len(), arena); + for (i, symbols) in sccs.iter().enumerate() { + // guess: every function has ~1 successor + let mut succs = Vec::with_capacity_in(symbols.len(), arena); + + for symbol in symbols { + for s in successors(symbol) { + let c = symbol_to_component[&s]; + + // don't insert self to prevent cycles + if c != i { + succs.push(c); + } + } + } + + succs.sort_unstable(); + succs.dedup(); + + component_to_successors.push(succs); + } + + let mut components = Vec::with_capacity_in(component_to_successors.len(), arena); + components.extend(0..component_to_successors.len()); + + let mut groups = Vec::new_in(arena); + + let component_to_successors = &component_to_successors; + match ven_graph::topological_sort_into_groups(&components, |c: &usize| { + component_to_successors[*c].iter().copied() + }) { + Ok(component_groups) => { + let mut component_to_group = bumpalo::vec![in arena; usize::MAX; components.len()]; + + // for each component, store which group it is in + for (group_index, component_group) in component_groups.iter().enumerate() { + for component in component_group { + component_to_group[*component] = group_index; + } + } + + // prepare groups + groups.reserve(component_groups.len()); + for _ in 0..component_groups.len() { + groups.push(Vec::new_in(arena)); + } + + for (key, proc) in procs { + let symbol = key.0; + let offset = param_map.get_param_offset(key.0, key.1); + + // the component this symbol is a part of + let component = symbol_to_component[&symbol]; + + // now find the group that this component belongs to + let group = component_to_group[component]; + + groups[group].push((proc, offset)); + } + } + Err((_groups, _remainder)) => { + unreachable!("because we find strongly-connected components first"); + } + } + + for group in groups.into_iter().rev() { + // This is a fixed-point analysis + // + // all functions initiall own all their parameters + // through a series of checks and heuristics, some arguments are set to borrowed + // when that doesn't lead to conflicts the change is kept, otherwise it may be reverted + // + // when the signatures no longer change, the analysis stops and returns the signatures + loop { + for (proc, param_offset) in group.iter() { + env.collect_proc(&mut param_map, proc, *param_offset); + } + + if !env.modified { + // if there were no modifications, we're done + break; + } else { + // otherwise see if there are changes after another iteration + env.modified = false; + } + } + } + + param_map } -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub enum Key<'a> { - Declaration(Symbol, ProcLayout<'a>), - JoinPoint(JoinPointId), +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub struct ParamOffset(usize); + +impl From for usize { + fn from(id: ParamOffset) -> Self { + id.0 as usize + } } -#[derive(Debug, Clone, Default)] +#[derive(Debug)] +struct DeclarationToIndex<'a> { + elements: Vec<'a, ((Symbol, ProcLayout<'a>), ParamOffset)>, +} + +impl<'a> DeclarationToIndex<'a> { + fn new(arena: &'a Bump, procs: &MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>) -> (Self, usize) { + let mut declaration_to_index = Vec::with_capacity_in(procs.len(), arena); + + let mut i = 0; + for key in procs.keys().copied() { + declaration_to_index.push((key, ParamOffset(i))); + + i += key.1.arguments.len(); + } + + declaration_to_index.sort_unstable_by_key(|t| t.0 .0); + + ( + DeclarationToIndex { + elements: declaration_to_index, + }, + i, + ) + } + + fn get_param_offset( + &self, + needle_symbol: Symbol, + needle_layout: ProcLayout<'a>, + ) -> ParamOffset { + if let Ok(middle_index) = self + .elements + .binary_search_by_key(&needle_symbol, |t| t.0 .0) + { + // first, iterate backward until we hit a different symbol + let backward = self.elements[..middle_index].iter().rev(); + + for ((symbol, proc_layout), param_offset) in backward { + if *symbol != needle_symbol { + break; + } else if *proc_layout == needle_layout { + return *param_offset; + } + } + + // if not found, iterate forward until we find our combo + let forward = self.elements[middle_index..].iter(); + + for ((symbol, proc_layout), param_offset) in forward { + if *symbol != needle_symbol { + break; + } else if *proc_layout == needle_layout { + return *param_offset; + } + } + } + unreachable!( + "symbol/layout {:?} {:?} combo must be in DeclarationToIndex", + needle_symbol, needle_layout + ) + } +} + +#[derive(Debug)] pub struct ParamMap<'a> { - items: MutMap, &'a [Param<'a>]>, -} - -impl<'a> IntoIterator for ParamMap<'a> { - type Item = (Key<'a>, &'a [Param<'a>]); - type IntoIter = , &'a [Param<'a>]> as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.items.into_iter() - } -} - -impl<'a> IntoIterator for &'a ParamMap<'a> { - type Item = (&'a Key<'a>, &'a &'a [Param<'a>]); - type IntoIter = - <&'a std::collections::HashMap, &'a [Param<'a>]> as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.items.iter() - } + /// Map a (Symbol, ProcLayout) pair to the starting index in the `declarations` array + declaration_to_index: DeclarationToIndex<'a>, + /// the parameters of all functions in a single flat array. + /// + /// - the map above gives the index of the first parameter for the function + /// - the length of the ProcLayout's argument field gives the total number of parameters + /// + /// These can be read by taking a slice into this array, and can also be updated in-place + declarations: Vec<'a, Param<'a>>, + join_points: MutMap]>, } impl<'a> ParamMap<'a> { - pub fn get_symbol(&self, symbol: Symbol, layout: ProcLayout<'a>) -> Option<&'a [Param<'a>]> { - let key = Key::Declaration(symbol, layout); - - self.items.get(&key).copied() + pub fn get_param_offset(&self, symbol: Symbol, layout: ProcLayout<'a>) -> ParamOffset { + self.declaration_to_index.get_param_offset(symbol, layout) } - pub fn get_join_point(&self, id: JoinPointId) -> &'a [Param<'a>] { - let key = Key::JoinPoint(id); - match self.items.get(&key) { + pub fn get_symbol(&self, symbol: Symbol, layout: ProcLayout<'a>) -> Option<&[Param<'a>]> { + // let index: usize = self.declaration_to_index[&(symbol, layout)].into(); + let index: usize = self.get_param_offset(symbol, layout).into(); + + self.declarations.get(index..index + layout.arguments.len()) + } + + pub fn get_join_point(&self, id: JoinPointId) -> &'a [Param<'a>] { + match self.join_points.get(&id) { Some(slice) => slice, None => unreachable!("join point not in param map: {:?}", id), } } + + pub fn iter_symbols(&'a self) -> impl Iterator { + self.declaration_to_index.elements.iter().map(|t| &t.0 .0) + } } impl<'a> ParamMap<'a> { @@ -156,11 +314,16 @@ impl<'a> ParamMap<'a> { self.visit_proc_always_owned(arena, proc, key); return; } - let already_in_there = self.items.insert( - Key::Declaration(proc.name, key.1), - Self::init_borrow_args(arena, proc.args), - ); - debug_assert!(already_in_there.is_none()); + + let index: usize = self.get_param_offset(key.0, key.1).into(); + + for (i, param) in Self::init_borrow_args(arena, proc.args) + .iter() + .copied() + .enumerate() + { + self.declarations[index + i] = param; + } self.visit_stmt(arena, proc.name, &proc.body); } @@ -171,11 +334,15 @@ impl<'a> ParamMap<'a> { proc: &Proc<'a>, key: (Symbol, ProcLayout<'a>), ) { - let already_in_there = self.items.insert( - Key::Declaration(proc.name, key.1), - Self::init_borrow_args_always_owned(arena, proc.args), - ); - debug_assert!(already_in_there.is_none()); + let index: usize = self.get_param_offset(key.0, key.1).into(); + + for (i, param) in Self::init_borrow_args_always_owned(arena, proc.args) + .iter() + .copied() + .enumerate() + { + self.declarations[index + i] = param; + } self.visit_stmt(arena, proc.name, &proc.body); } @@ -193,10 +360,8 @@ impl<'a> ParamMap<'a> { remainder: v, body: b, } => { - let already_in_there = self - .items - .insert(Key::JoinPoint(*j), Self::init_borrow_params(arena, xs)); - debug_assert!(already_in_there.is_none()); + self.join_points + .insert(*j, Self::init_borrow_params(arena, xs)); stack.push(v); stack.push(b); @@ -204,10 +369,6 @@ impl<'a> ParamMap<'a> { Let(_, _, _, cont) => { stack.push(cont); } - Invoke { pass, fail, .. } => { - stack.push(pass); - stack.push(fail); - } Switch { branches, default_branch, @@ -218,7 +379,7 @@ impl<'a> ParamMap<'a> { } Refcounting(_, _) => unreachable!("these have not been introduced yet"), - Ret(_) | Resume(_) | Jump(_, _) | RuntimeError(_) => { + Ret(_) | Jump(_, _) | RuntimeError(_) => { // these are terminal, do nothing } } @@ -233,7 +394,6 @@ struct BorrowInfState<'a> { param_set: MutSet, owned: MutMap>, modified: bool, - param_map: ParamMap<'a>, arena: &'a Bump, } @@ -241,14 +401,30 @@ impl<'a> BorrowInfState<'a> { pub fn own_var(&mut self, x: Symbol) { let current = self.owned.get_mut(&self.current_proc).unwrap(); - if current.contains(&x) { - // do nothing - } else { - current.insert(x); + if current.insert(x) { + // entered if key was not yet present. If so, the set is modified, + // hence we set this flag self.modified = true; } } + /// if the extracted value is owned, then the surrounding structure must be too + fn if_is_owned_then_own(&mut self, extracted: Symbol, structure: Symbol) { + match self.owned.get_mut(&self.current_proc) { + None => unreachable!( + "the current procedure symbol {:?} is not in the owned map", + self.current_proc + ), + Some(set) => { + if set.contains(&extracted) && set.insert(structure) { + // entered if key was not yet present. If so, the set is modified, + // hence we set this flag + self.modified = true; + } + } + } + } + fn is_owned(&self, x: Symbol) -> bool { match self.owned.get(&self.current_proc) { None => unreachable!( @@ -259,30 +435,52 @@ impl<'a> BorrowInfState<'a> { } } - fn update_param_map(&mut self, k: Key<'a>) { - let arena = self.arena; - if let Some(ps) = self.param_map.items.get(&k) { - let ps = Vec::from_iter_in( - ps.iter().map(|p| { - if !p.borrow { - p.clone() - } else if self.is_owned(p.symbol) { - self.modified = true; - let mut p = p.clone(); - p.borrow = false; + fn update_param_map_help(&mut self, ps: &[Param<'a>]) -> &'a [Param<'a>] { + let mut new_ps = Vec::with_capacity_in(ps.len(), self.arena); + new_ps.extend(ps.iter().map(|p| { + if !p.borrow { + *p + } else if self.is_owned(p.symbol) { + self.modified = true; + let mut p = *p; + p.borrow = false; - p - } else { - p.clone() - } - }), - arena, - ); + p + } else { + *p + } + })); - self.param_map.items.insert(k, ps.into_bump_slice()); + new_ps.into_bump_slice() + } + + fn update_param_map_declaration( + &mut self, + param_map: &mut ParamMap<'a>, + start: ParamOffset, + length: usize, + ) { + let index: usize = start.into(); + let ps = &mut param_map.declarations[index..][..length]; + + for p in ps.iter_mut() { + if !p.borrow { + // do nothing + } else if self.is_owned(p.symbol) { + self.modified = true; + p.borrow = false; + } else { + // do nothing + } } } + fn update_param_map_join_point(&mut self, param_map: &mut ParamMap<'a>, id: JoinPointId) { + let ps = param_map.join_points[&id]; + let new_ps = self.update_param_map_help(ps); + param_map.join_points.insert(id, new_ps); + } + /// This looks at an application `f x1 x2 x3` /// If the parameter (based on the definition of `f`) is owned, /// then the argument must also be owned @@ -341,13 +539,13 @@ impl<'a> BorrowInfState<'a> { } } - /// This looks at the assignement + /// This looks at the assignment /// /// let z = e in ... /// /// and determines whether z and which of the symbols used in e /// must be taken as owned parameters - fn collect_call(&mut self, z: Symbol, e: &crate::ir::Call<'a>) { + fn collect_call(&mut self, param_map: &mut ParamMap<'a>, z: Symbol, e: &crate::ir::Call<'a>) { use crate::ir::CallType::*; let crate::ir::Call { @@ -365,8 +563,7 @@ impl<'a> BorrowInfState<'a> { let top_level = ProcLayout::new(self.arena, arg_layouts, *ret_layout); // get the borrow signature of the applied function - let ps = self - .param_map + let ps = param_map .get_symbol(*name, top_level) .expect("function is defined"); @@ -382,6 +579,7 @@ impl<'a> BorrowInfState<'a> { ps.len(), arguments.len() ); + self.own_args_using_params(arguments, ps); } @@ -412,7 +610,7 @@ impl<'a> BorrowInfState<'a> { match op { ListMap | ListKeepIf | ListKeepOks | ListKeepErrs => { - match self.param_map.get_symbol(arguments[1], closure_layout) { + match param_map.get_symbol(arguments[1], closure_layout) { Some(function_ps) => { // own the list if the function wants to own the element if !function_ps[0].borrow { @@ -428,7 +626,7 @@ impl<'a> BorrowInfState<'a> { } } ListMapWithIndex => { - match self.param_map.get_symbol(arguments[1], closure_layout) { + match param_map.get_symbol(arguments[1], closure_layout) { Some(function_ps) => { // own the list if the function wants to own the element if !function_ps[1].borrow { @@ -443,7 +641,7 @@ impl<'a> BorrowInfState<'a> { None => unreachable!(), } } - ListMap2 => match self.param_map.get_symbol(arguments[2], closure_layout) { + ListMap2 => match param_map.get_symbol(arguments[2], closure_layout) { Some(function_ps) => { // own the lists if the function wants to own the element if !function_ps[0].borrow { @@ -461,7 +659,7 @@ impl<'a> BorrowInfState<'a> { } None => unreachable!(), }, - ListMap3 => match self.param_map.get_symbol(arguments[3], closure_layout) { + ListMap3 => match param_map.get_symbol(arguments[3], closure_layout) { Some(function_ps) => { // own the lists if the function wants to own the element if !function_ps[0].borrow { @@ -482,7 +680,7 @@ impl<'a> BorrowInfState<'a> { None => unreachable!(), }, ListSortWith => { - match self.param_map.get_symbol(arguments[1], closure_layout) { + match param_map.get_symbol(arguments[1], closure_layout) { Some(function_ps) => { // always own the input list self.own_var(arguments[0]); @@ -496,7 +694,7 @@ impl<'a> BorrowInfState<'a> { } } ListWalk | ListWalkUntil | ListWalkBackwards | DictWalk => { - match self.param_map.get_symbol(arguments[2], closure_layout) { + match param_map.get_symbol(arguments[2], closure_layout) { Some(function_ps) => { // own the data structure if the function wants to own the element if !function_ps[0].borrow { @@ -538,7 +736,7 @@ impl<'a> BorrowInfState<'a> { } } - fn collect_expr(&mut self, z: Symbol, e: &Expr<'a>) { + fn collect_expr(&mut self, param_map: &mut ParamMap<'a>, z: Symbol, e: &Expr<'a>) { use Expr::*; match e { @@ -566,50 +764,44 @@ impl<'a> BorrowInfState<'a> { self.own_var(z); } - Call(call) => self.collect_call(z, call), + Call(call) => self.collect_call(param_map, z, call), Literal(_) | RuntimeErrorFunction(_) => {} StructAtIndex { structure: x, .. } => { // if the structure (record/tag/array) is owned, the extracted value is - if self.is_owned(*x) { - self.own_var(z); - } + self.if_is_owned_then_own(*x, z); // if the extracted value is owned, the structure must be too - if self.is_owned(z) { - self.own_var(*x); - } + self.if_is_owned_then_own(z, *x); } UnionAtIndex { structure: x, .. } => { // if the structure (record/tag/array) is owned, the extracted value is - if self.is_owned(*x) { - self.own_var(z); - } + self.if_is_owned_then_own(*x, z); // if the extracted value is owned, the structure must be too - if self.is_owned(z) { - self.own_var(*x); - } + self.if_is_owned_then_own(z, *x); } GetTagId { structure: x, .. } => { // if the structure (record/tag/array) is owned, the extracted value is - if self.is_owned(*x) { - self.own_var(z); - } + self.if_is_owned_then_own(*x, z); // if the extracted value is owned, the structure must be too - if self.is_owned(z) { - self.own_var(*x); - } + self.if_is_owned_then_own(z, *x); } } } #[allow(clippy::many_single_char_names)] - fn preserve_tail_call(&mut self, x: Symbol, v: &Expr<'a>, b: &Stmt<'a>) { + fn preserve_tail_call( + &mut self, + param_map: &mut ParamMap<'a>, + x: Symbol, + v: &Expr<'a>, + b: &Stmt<'a>, + ) { if let ( Expr::Call(crate::ir::Call { call_type: @@ -630,7 +822,7 @@ impl<'a> BorrowInfState<'a> { if self.current_proc == *g && x == *z { // anonymous functions (for which the ps may not be known) // can never be tail-recursive, so this is fine - if let Some(ps) = self.param_map.get_symbol(*g, top_level) { + if let Some(ps) = param_map.get_symbol(*g, top_level) { self.own_params_using_args(ys, ps) } } @@ -649,7 +841,7 @@ impl<'a> BorrowInfState<'a> { } } - fn collect_stmt(&mut self, stmt: &Stmt<'a>) { + fn collect_stmt(&mut self, param_map: &mut ParamMap<'a>, stmt: &Stmt<'a>) { use Stmt::*; match stmt { @@ -661,38 +853,39 @@ impl<'a> BorrowInfState<'a> { } => { let old = self.param_set.clone(); self.update_param_set(ys); - self.collect_stmt(v); + self.collect_stmt(param_map, v); self.param_set = old; - self.update_param_map(Key::JoinPoint(*j)); + self.update_param_map_join_point(param_map, *j); - self.collect_stmt(b); + self.collect_stmt(param_map, b); } - Let(x, v, _, b) => { - self.collect_stmt(b); - self.collect_expr(*x, v); - self.preserve_tail_call(*x, v, b); - } + Let(x, v, _, mut b) => { + let mut stack = Vec::new_in(self.arena); - Invoke { - symbol, - call, - layout: _, - pass, - fail, - exception_id: _, - } => { - self.collect_stmt(pass); - self.collect_stmt(fail); + stack.push((*x, v)); - self.collect_call(*symbol, call); + while let Stmt::Let(symbol, expr, _, tail) = b { + b = tail; + stack.push((*symbol, expr)); + } - // TODO how to preserve the tail call of an invoke? - // self.preserve_tail_call(*x, v, b); + self.collect_stmt(param_map, b); + + let mut it = stack.into_iter().rev(); + + // collect the final expr, and see if we need to preserve a tail call + let (x, v) = it.next().unwrap(); + self.collect_expr(param_map, x, v); + self.preserve_tail_call(param_map, x, v, b); + + for (x, v) in it { + self.collect_expr(param_map, x, v); + } } Jump(j, ys) => { - let ps = self.param_map.get_join_point(*j); + let ps = param_map.get_join_point(*j); // for making sure the join point can reuse self.own_args_using_params(ys, ps); @@ -706,19 +899,24 @@ impl<'a> BorrowInfState<'a> { .. } => { for (_, _, b) in branches.iter() { - self.collect_stmt(b); + self.collect_stmt(param_map, b); } - self.collect_stmt(default_branch.1); + self.collect_stmt(param_map, default_branch.1); } Refcounting(_, _) => unreachable!("these have not been introduced yet"), - Ret(_) | RuntimeError(_) | Resume(_) => { + Ret(_) | RuntimeError(_) => { // these are terminal, do nothing } } } - fn collect_proc(&mut self, proc: &Proc<'a>, layout: ProcLayout<'a>) { + fn collect_proc( + &mut self, + param_map: &mut ParamMap<'a>, + proc: &Proc<'a>, + param_offset: ParamOffset, + ) { let old = self.param_set.clone(); let ys = Vec::from_iter_in(proc.args.iter().map(|t| t.1), self.arena).into_bump_slice(); @@ -728,8 +926,8 @@ impl<'a> BorrowInfState<'a> { // ensure that current_proc is in the owned map self.owned.entry(proc.name).or_default(); - self.collect_stmt(&proc.body); - self.update_param_map(Key::Declaration(proc.name, layout)); + self.collect_stmt(param_map, &proc.body); + self.update_param_map_declaration(param_map, param_offset, proc.args.len()); self.param_set = old; } @@ -800,10 +998,13 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin | NumIntCast => arena.alloc_slice_copy(&[irrelevant]), + NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]), + NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]), StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]), - StrStartsWithCodePoint => arena.alloc_slice_copy(&[borrowed, irrelevant]), + StrStartsWithCodePt => arena.alloc_slice_copy(&[borrowed, irrelevant]), StrFromUtf8 => arena.alloc_slice_copy(&[owned]), - StrToBytes => arena.alloc_slice_copy(&[owned]), + StrFromUtf8Range => arena.alloc_slice_copy(&[borrowed, irrelevant]), + StrToUtf8 => arena.alloc_slice_copy(&[owned]), StrFromInt | StrFromFloat => arena.alloc_slice_copy(&[irrelevant]), Hash => arena.alloc_slice_copy(&[borrowed, irrelevant]), DictSize => arena.alloc_slice_copy(&[borrowed]), @@ -823,3 +1024,80 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { ExpectTrue => arena.alloc_slice_copy(&[irrelevant]), } } + +fn make_successor_mapping<'a>( + arena: &'a Bump, + procs: &MutMap<(Symbol, ProcLayout<'_>), Proc<'a>>, +) -> MutMap> { + let mut result = MutMap::with_capacity_and_hasher(procs.len(), default_hasher()); + + for (key, proc) in procs { + let mut call_info = CallInfo { + keys: Vec::new_in(arena), + }; + call_info_stmt(arena, &proc.body, &mut call_info); + + let mut keys = call_info.keys; + keys.sort_unstable(); + keys.dedup(); + + result.insert(key.0, keys); + } + + result +} + +struct CallInfo<'a> { + keys: Vec<'a, Symbol>, +} + +fn call_info_call<'a>(call: &crate::ir::Call<'a>, info: &mut CallInfo<'a>) { + use crate::ir::CallType::*; + + match call.call_type { + ByName { name, .. } => { + info.keys.push(name); + } + Foreign { .. } => {} + LowLevel { .. } => {} + HigherOrderLowLevel { .. } => {} + } +} + +fn call_info_stmt<'a>(arena: &'a Bump, stmt: &Stmt<'a>, info: &mut CallInfo<'a>) { + use Stmt::*; + + let mut stack = bumpalo::vec![ in arena; stmt ]; + + while let Some(stmt) = stack.pop() { + match stmt { + Join { + remainder: v, + body: b, + .. + } => { + stack.push(v); + stack.push(b); + } + Let(_, expr, _, cont) => { + if let Expr::Call(call) = expr { + call_info_call(call, info); + } + stack.push(cont); + } + Switch { + branches, + default_branch, + .. + } => { + stack.extend(branches.iter().map(|b| &b.2)); + stack.push(default_branch.1); + } + Refcounting(_, _) => unreachable!("these have not been introduced yet"), + + Ret(_) | Jump(_, _) | RuntimeError(_) => { + // these are terminal, do nothing + } + } + } +} diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index d374e99463..67a9ab8bca 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -22,7 +22,8 @@ fn compile<'a>(raw_branches: Vec<(Guard<'a>, Pattern<'a>, u64)>) -> DecisionTree .into_iter() .map(|(guard, pattern, index)| Branch { goal: index, - patterns: vec![(Vec::new(), guard, pattern)], + guard, + patterns: vec![(Vec::new(), pattern)], }) .collect(); @@ -33,9 +34,8 @@ fn compile<'a>(raw_branches: Vec<(Guard<'a>, Pattern<'a>, u64)>) -> DecisionTree pub enum Guard<'a> { NoGuard, Guard { - /// Symbol that stores a boolean - /// when true this branch is picked, otherwise skipped - symbol: Symbol, + /// pattern + pattern: Pattern<'a>, /// after assigning to symbol, the stmt jumps to this label id: JoinPointId, stmt: Stmt<'a>, @@ -60,22 +60,19 @@ enum DecisionTree<'a> { #[derive(Clone, Debug, PartialEq)] enum GuardedTest<'a> { - TestGuarded { - test: Test<'a>, - - /// after assigning to symbol, the stmt jumps to this label - id: JoinPointId, - stmt: Stmt<'a>, - }, // e.g. `_ if True -> ...` GuardedNoTest { + /// pattern + pattern: Pattern<'a>, /// after assigning to symbol, the stmt jumps to this label id: JoinPointId, + /// body stmt: Stmt<'a>, }, TestNotGuarded { test: Test<'a>, }, + Placeholder, } #[derive(Clone, Debug, PartialEq)] @@ -136,16 +133,16 @@ impl<'a> Hash for Test<'a> { impl<'a> Hash for GuardedTest<'a> { fn hash(&self, state: &mut H) { match self { - GuardedTest::TestGuarded { test, .. } => { + GuardedTest::GuardedNoTest { id, .. } => { + state.write_u8(1); + id.hash(state); + } + GuardedTest::TestNotGuarded { test } => { state.write_u8(0); test.hash(state); } - GuardedTest::GuardedNoTest { .. } => { - state.write_u8(1); - } - GuardedTest::TestNotGuarded { test } => { + GuardedTest::Placeholder => { state.write_u8(2); - test.hash(state); } } } @@ -156,22 +153,70 @@ impl<'a> Hash for GuardedTest<'a> { #[derive(Clone, Debug, PartialEq)] struct Branch<'a> { goal: Label, - patterns: Vec<(Vec, Guard<'a>, Pattern<'a>)>, + guard: Guard<'a>, + patterns: Vec<(Vec, Pattern<'a>)>, } fn to_decision_tree(raw_branches: Vec) -> DecisionTree { let branches: Vec<_> = raw_branches.into_iter().map(flatten_patterns).collect(); + debug_assert!(!branches.is_empty()); + match check_for_match(&branches) { - Some(goal) => DecisionTree::Match(goal), - None => { + Match::Exact(goal) => DecisionTree::Match(goal), + + Match::GuardOnly => { + // the first branch has no more tests to do, but it has an if-guard + + let mut branches = branches; + let first = branches.remove(0); + + match first.guard { + Guard::NoGuard => unreachable!(), + + Guard::Guard { id, stmt, pattern } => { + let guarded_test = GuardedTest::GuardedNoTest { id, stmt, pattern }; + + // the guard test does not have a path + let path = vec![]; + + // we expect none of the patterns need tests, those decisions should have been made already + debug_assert!(first + .patterns + .iter() + .all(|(_, pattern)| !needs_tests(pattern))); + + let default = if branches.is_empty() { + None + } else { + Some(Box::new(to_decision_tree(branches))) + }; + + DecisionTree::Decision { + path, + edges: vec![(guarded_test, DecisionTree::Match(first.goal))], + default, + } + } + } + } + + Match::None => { // must clone here to release the borrow on `branches` let path = pick_path(&branches).clone(); + + let bs = branches.clone(); let (edges, fallback) = gather_edges(branches, &path); let mut decision_edges: Vec<_> = edges .into_iter() - .map(|(a, b)| (a, to_decision_tree(b))) + .map(|(test, branches)| { + if bs == branches { + panic!(); + } else { + (test, to_decision_tree(branches)) + } + }) .collect(); match (decision_edges.as_slice(), fallback.as_slice()) { @@ -181,21 +226,55 @@ fn to_decision_tree(raw_branches: Vec) -> DecisionTree { // get the `_decision_tree` without cloning decision_edges.pop().unwrap().1 } - (_, []) => DecisionTree::Decision { - path, - edges: decision_edges, - default: None, - }, + (_, []) => break_out_guard(path, decision_edges, None), ([], _) => { // should be guaranteed by the patterns debug_assert!(!fallback.is_empty()); to_decision_tree(fallback) } - (_, _) => DecisionTree::Decision { + (_, _) => break_out_guard( path, - edges: decision_edges, - default: Some(Box::new(to_decision_tree(fallback))), - }, + decision_edges, + Some(Box::new(to_decision_tree(fallback))), + ), + } + } + } +} + +/// Give a guard it's own Decision +fn break_out_guard<'a>( + path: Vec, + mut edges: Vec<(GuardedTest<'a>, DecisionTree<'a>)>, + default: Option>>, +) -> DecisionTree<'a> { + match edges + .iter() + .position(|(t, _)| matches!(t, GuardedTest::Placeholder)) + { + None => DecisionTree::Decision { + path, + edges, + default, + }, + Some(index) => { + let (a, b) = edges.split_at_mut(index + 1); + + let new_default = break_out_guard(path.clone(), b.to_vec(), default); + + let mut left = a.to_vec(); + let guard = left.pop().unwrap(); + + let help = DecisionTree::Decision { + path: path.clone(), + edges: vec![guard], + default: Some(Box::new(new_default)), + }; + + DecisionTree::Decision { + path, + edges: left, + default: Some(Box::new(help)), } } } @@ -205,10 +284,14 @@ fn guarded_tests_are_complete(tests: &[GuardedTest]) -> bool { let length = tests.len(); debug_assert!(length > 0); + let no_guard = tests + .iter() + .all(|t| matches!(t, GuardedTest::TestNotGuarded { .. })); + match tests.last().unwrap() { - GuardedTest::TestGuarded { .. } => false, + GuardedTest::Placeholder => false, GuardedTest::GuardedNoTest { .. } => false, - GuardedTest::TestNotGuarded { test } => tests_are_complete_help(test, length), + GuardedTest::TestNotGuarded { test } => no_guard && tests_are_complete_help(test, length), } } @@ -231,16 +314,16 @@ fn flatten_patterns(branch: Branch) -> Branch { } Branch { - goal: branch.goal, patterns: result, + ..branch } } fn flatten<'a>( - path_pattern: (Vec, Guard<'a>, Pattern<'a>), - path_patterns: &mut Vec<(Vec, Guard<'a>, Pattern<'a>)>, + path_pattern: (Vec, Pattern<'a>), + path_patterns: &mut Vec<(Vec, Pattern<'a>)>, ) { - match path_pattern.2 { + match path_pattern.1 { Pattern::AppliedTag { union, arguments, @@ -257,7 +340,6 @@ fn flatten<'a>( // NOTE here elm will unbox, but we don't use that path_patterns.push(( path, - path_pattern.1.clone(), Pattern::AppliedTag { union, arguments, @@ -274,15 +356,7 @@ fn flatten<'a>( tag_id, }); - flatten( - ( - new_path, - // same guard here? - path_pattern.1.clone(), - arg_pattern.clone(), - ), - path_patterns, - ); + flatten((new_path, arg_pattern.clone()), path_patterns); } } } @@ -299,21 +373,33 @@ fn flatten<'a>( /// path. If that is the case we give the resulting label and a mapping from free /// variables to "how to get their value". So a pattern like (Just (x,_)) will give /// us something like ("x" => value.0.0) -fn check_for_match(branches: &[Branch]) -> Option