Merge branch 'trunk' of github.com:rtfeldman/roc into editor-let-value

This commit is contained in:
Anton-4 2021-08-21 12:05:59 +02:00
commit 169520f956
301 changed files with 15511 additions and 10303 deletions

View file

@ -1,4 +1,7 @@
on: [pull_request] on:
pull_request:
paths-ignore:
- '**.md'
name: Benchmarks name: Benchmarks
@ -7,7 +10,7 @@ env:
jobs: jobs:
prep-dependency-container: prep-dependency-container:
name: cd cli; cargo criterion name: benchmark roc programs
runs-on: [self-hosted, i7-6700K] runs-on: [self-hosted, i7-6700K]
timeout-minutes: 60 timeout-minutes: 60
env: env:
@ -31,5 +34,12 @@ jobs:
- name: on current branch; prepare a self-contained benchmark folder - name: on current branch; prepare a self-contained benchmark folder
run: ./ci/safe-earthly.sh +prep-bench-folder run: ./ci/safe-earthly.sh +prep-bench-folder
- name: execute benchmarks with regression check - uses: actions-rs/toolchain@v1
run: ./ci/bench-runner.sh 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

View file

@ -1,4 +1,7 @@
on: [pull_request] on:
pull_request:
paths-ignore:
- '**.md'
name: CI name: CI

24
.github/workflows/spellcheck.yml vendored Normal file
View file

@ -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

3
.gitignore vendored
View file

@ -34,4 +34,5 @@ sccache_dir
# self-contained benchmark folder # self-contained benchmark folder
bench-folder* bench-folder*
# earthly
earthly_log.txt

View file

@ -1,12 +1,10 @@
# Building the Roc compiler from source # 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: 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) * Python 2.7 (Windows only), `python-is-python3` (Ubuntu)
* [Zig](https://ziglang.org/), see below for version * [Zig](https://ziglang.org/), see below for version
* LLVM, 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. 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 ### libcxb libraries
You may see an error like this during builds: 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 ### 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 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. 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"] 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 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. That's it! Enjoy the faster builds.

View file

@ -10,9 +10,8 @@ Check [Build from source](BUILDING_FROM_SOURCE.md) for instructions.
## Running Tests ## 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 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 ## 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! - 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? ## Can we do better?

36
Cargo.lock generated
View file

@ -402,6 +402,7 @@ dependencies = [
"bumpalo", "bumpalo",
"criterion", "criterion",
"inlinable_string", "inlinable_string",
"rlimit",
"roc_cli", "roc_cli",
"roc_collections", "roc_collections",
"roc_load", "roc_load",
@ -2979,6 +2980,15 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157"
[[package]]
name = "rlimit"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc0bf25554376fd362f54332b8410a625c71f15445bca32ffdfdf4ec9ac91726"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "roc_build" name = "roc_build"
version = "0.1.0" version = "0.1.0"
@ -3037,7 +3047,6 @@ dependencies = [
"im 14.3.0", "im 14.3.0",
"im-rc 14.3.0", "im-rc 14.3.0",
"indoc 0.3.6", "indoc 0.3.6",
"inlinable_string",
"maplit", "maplit",
"pretty_assertions 0.5.1", "pretty_assertions 0.5.1",
"quickcheck 0.8.5", "quickcheck 0.8.5",
@ -3065,7 +3074,6 @@ dependencies = [
"im-rc 14.3.0", "im-rc 14.3.0",
"indoc 0.3.6", "indoc 0.3.6",
"inkwell 0.1.0", "inkwell 0.1.0",
"inlinable_string",
"libc", "libc",
"libloading 0.6.7", "libloading 0.6.7",
"maplit", "maplit",
@ -3140,6 +3148,7 @@ dependencies = [
"roc_collections", "roc_collections",
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_parse",
"roc_region", "roc_region",
"roc_types", "roc_types",
] ]
@ -3161,7 +3170,6 @@ dependencies = [
"im 15.0.0", "im 15.0.0",
"im-rc 15.0.0", "im-rc 15.0.0",
"indoc 1.0.3", "indoc 1.0.3",
"inlinable_string",
"libc", "libc",
"log", "log",
"maplit", "maplit",
@ -3207,7 +3215,6 @@ dependencies = [
"im 14.3.0", "im 14.3.0",
"im-rc 14.3.0", "im-rc 14.3.0",
"indoc 0.3.6", "indoc 0.3.6",
"inlinable_string",
"maplit", "maplit",
"pretty_assertions 0.5.1", "pretty_assertions 0.5.1",
"quickcheck 0.8.5", "quickcheck 0.8.5",
@ -3226,7 +3233,6 @@ dependencies = [
"im 14.3.0", "im 14.3.0",
"im-rc 14.3.0", "im-rc 14.3.0",
"indoc 0.3.6", "indoc 0.3.6",
"inlinable_string",
"itertools 0.9.0", "itertools 0.9.0",
"libc", "libc",
"libloading 0.6.7", "libloading 0.6.7",
@ -3265,7 +3271,6 @@ dependencies = [
"im-rc 14.3.0", "im-rc 14.3.0",
"indoc 0.3.6", "indoc 0.3.6",
"inkwell 0.1.0", "inkwell 0.1.0",
"inlinable_string",
"libc", "libc",
"maplit", "maplit",
"morphic_lib", "morphic_lib",
@ -3291,6 +3296,10 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "roc_ident"
version = "0.1.0"
[[package]] [[package]]
name = "roc_load" name = "roc_load"
version = "0.1.0" version = "0.1.0"
@ -3298,7 +3307,6 @@ dependencies = [
"bumpalo", "bumpalo",
"crossbeam", "crossbeam",
"indoc 0.3.6", "indoc 0.3.6",
"inlinable_string",
"maplit", "maplit",
"morphic_lib", "morphic_lib",
"num_cpus", "num_cpus",
@ -3329,12 +3337,13 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"indoc 0.3.6", "indoc 0.3.6",
"inlinable_string",
"lazy_static", "lazy_static",
"maplit", "maplit",
"pretty_assertions 0.5.1", "pretty_assertions 0.5.1",
"roc_collections", "roc_collections",
"roc_ident",
"roc_region", "roc_region",
"static_assertions",
] ]
[[package]] [[package]]
@ -3362,6 +3371,7 @@ dependencies = [
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"ven_ena", "ven_ena",
"ven_graph",
"ven_pretty", "ven_pretty",
] ]
@ -3372,7 +3382,6 @@ dependencies = [
"bumpalo", "bumpalo",
"encode_unicode", "encode_unicode",
"indoc 0.3.6", "indoc 0.3.6",
"inlinable_string",
"pretty_assertions 0.5.1", "pretty_assertions 0.5.1",
"quickcheck 0.8.5", "quickcheck 0.8.5",
"quickcheck_macros 0.8.0", "quickcheck_macros 0.8.0",
@ -3386,7 +3395,6 @@ name = "roc_problem"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"indoc 0.3.6", "indoc 0.3.6",
"inlinable_string",
"maplit", "maplit",
"pretty_assertions 0.5.1", "pretty_assertions 0.5.1",
"quickcheck 0.8.5", "quickcheck 0.8.5",
@ -3410,7 +3418,6 @@ dependencies = [
"im 14.3.0", "im 14.3.0",
"im-rc 14.3.0", "im-rc 14.3.0",
"indoc 0.3.6", "indoc 0.3.6",
"inlinable_string",
"maplit", "maplit",
"pretty_assertions 0.5.1", "pretty_assertions 0.5.1",
"quickcheck 0.8.5", "quickcheck 0.8.5",
@ -3456,16 +3463,12 @@ dependencies = [
[[package]] [[package]]
name = "roc_std" name = "roc_std"
version = "0.1.0" version = "0.1.0"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "roc_types" name = "roc_types"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"indoc 0.3.6", "indoc 0.3.6",
"inlinable_string",
"maplit", "maplit",
"pretty_assertions 0.5.1", "pretty_assertions 0.5.1",
"quickcheck 0.8.5", "quickcheck 0.8.5",
@ -3473,6 +3476,7 @@ dependencies = [
"roc_collections", "roc_collections",
"roc_module", "roc_module",
"roc_region", "roc_region",
"static_assertions",
"ven_ena", "ven_ena",
] ]
@ -3953,7 +3957,6 @@ dependencies = [
"im-rc 14.3.0", "im-rc 14.3.0",
"indoc 0.3.6", "indoc 0.3.6",
"inkwell 0.1.0", "inkwell 0.1.0",
"inlinable_string",
"libc", "libc",
"libloading 0.6.7", "libloading 0.6.7",
"maplit", "maplit",
@ -3989,7 +3992,6 @@ dependencies = [
"im 14.3.0", "im 14.3.0",
"im-rc 14.3.0", "im-rc 14.3.0",
"indoc 0.3.6", "indoc 0.3.6",
"inlinable_string",
"libc", "libc",
"libloading 0.6.7", "libloading 0.6.7",
"pretty_assertions 0.5.1", "pretty_assertions 0.5.1",

View file

@ -1,5 +1,6 @@
[workspace] [workspace]
members = [ members = [
"compiler/ident",
"compiler/region", "compiler/region",
"compiler/collections", "compiler/collections",
"compiler/module", "compiler/module",
@ -32,6 +33,7 @@ members = [
"roc_std", "roc_std",
"docs", "docs",
] ]
exclude = [ "ci/bench-runner" ]
# Needed to be able to run `cargo run -p roc_cli --no-default-features` - # Needed to be able to run `cargo run -p roc_cli --no-default-features` -
# see www/build.sh for more. # see www/build.sh for more.
# #

View file

@ -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. 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. 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.

View file

@ -1,4 +1,4 @@
FROM rust:1.52-slim-buster FROM rust:1.54-slim-buster
WORKDIR /earthbuild WORKDIR /earthbuild
prep-debian: prep-debian:
@ -40,7 +40,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
# rustfmt # rustfmt
RUN rustup component add rustfmt RUN rustup component add rustfmt
# criterion # criterion
RUN cargo install --git https://github.com/Anton-4/cargo-criterion --branch main RUN cargo install cargo-criterion
# sccache # sccache
RUN apt -y install libssl-dev RUN apt -y install libssl-dev
RUN cargo install sccache RUN cargo install sccache

View file

@ -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 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 and Platforms
Applications are often built on a *framework.* Typically, both application and framework are written in the same language. Applications are often built on a *framework.* Typically, both application and framework are written in the same language.

View file

@ -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

View file

@ -3,22 +3,59 @@
version = 3 version = 3
[[package]] [[package]]
name = "base64" name = "aho-corasick"
version = "0.13.0" version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.6.1" version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.67" version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -27,74 +64,91 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chunked_transfer" name = "clap"
version = "1.4.0" version = "3.0.0-beta.2"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "errno" name = "clap_derive"
version = "0.2.7" version = "3.0.0-beta.2"
source = "registry+https://github.com/rust-lang/crates.io-index" 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 = [ dependencies = [
"errno-dragonfly",
"libc", "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", "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]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.50" version = "0.3.51"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062"
dependencies = [ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
@ -107,9 +161,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.92" version = "0.2.98"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714" checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
[[package]] [[package]]
name = "log" name = "log"
@ -121,28 +175,52 @@ dependencies = [
] ]
[[package]] [[package]]
name = "matches" name = "memchr"
version = "0.1.8" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.7.2" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]] [[package]]
name = "percent-encoding" name = "os_str_bytes"
version = "2.1.0" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.26" version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
dependencies = [ dependencies = [
"unicode-xid", "unicode-xid",
] ]
@ -156,6 +234,23 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "ring" name = "ring"
version = "0.16.20" version = "0.16.20"
@ -171,36 +266,6 @@ dependencies = [
"winapi", "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]] [[package]]
name = "spin" name = "spin"
version = "0.5.2" version = "0.5.2"
@ -208,10 +273,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]] [[package]]
name = "syn" name = "strsim"
version = "1.0.68" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" 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 = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -219,43 +290,40 @@ dependencies = [
] ]
[[package]] [[package]]
name = "tinyvec" name = "termcolor"
version = "1.2.0" version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [ dependencies = [
"tinyvec_macros", "winapi-util",
] ]
[[package]] [[package]]
name = "tinyvec_macros" name = "textwrap"
version = "0.1.0" version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789"
[[package]]
name = "unicode-bidi"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
dependencies = [ dependencies = [
"matches", "unicode-width",
] ]
[[package]] [[package]]
name = "unicode-normalization" name = "unicode-segmentation"
version = "0.1.17" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
dependencies = [
"tinyvec", [[package]]
] name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.1" version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
@ -264,38 +332,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]] [[package]]
name = "ureq" name = "vec_map"
version = "2.1.0" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fbeb1aabb07378cf0e084971a74f24241273304653184f54cdce113c0d7df1b" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
dependencies = [
"base64",
"chunked_transfer",
"log",
"once_cell",
"rustls",
"url",
"webpki",
"webpki-roots",
]
[[package]] [[package]]
name = "url" name = "version_check"
version = "2.2.1" version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
dependencies = [
"form_urlencoded",
"idna",
"matches",
"percent-encoding",
]
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.73" version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"wasm-bindgen-macro", "wasm-bindgen-macro",
@ -303,9 +355,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.73" version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"lazy_static", "lazy_static",
@ -318,9 +370,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.73" version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -328,9 +380,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.73" version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -341,39 +393,20 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.73" version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f"
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.50" version = "0.3.51"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "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]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"
@ -390,6 +423,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 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]] [[package]]
name = "winapi-x86_64-pc-windows-gnu" name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"

View file

@ -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"

256
ci/bench-runner/src/main.rs Normal file
View file

@ -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<String>, 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<String> {
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(&regressed_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<String> {
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<String> = HashSet::new();
let mut last_three_lines_queue: VecDeque<String> = 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<R: Read>(mut reader: R) -> Result<Digest, io::Error> {
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<String, io::Error> {
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<String, String> {
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
}
}

View file

@ -57,13 +57,12 @@ roc_reporting = { path = "../compiler/reporting" }
roc_editor = { path = "../editor", optional = true } 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 # TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it
clap = { git = "https://github.com/rtfeldman/clap", branch = "master" } clap = { 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 = { git = "https://github.com/rtfeldman/rustyline", tag = "prompt-fix" }
rustyline-derive = { 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 = "14" # im and im-rc should always have the same version!
im-rc = "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"] } bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1"
libc = "0.2" libc = "0.2"
libloading = "0.6" libloading = "0.6"

View file

@ -1,15 +1,28 @@
use std::time::Duration;
use cli_utils::bench_utils::{ use cli_utils::bench_utils::{
bench_cfold, bench_deriv, bench_nqueens, bench_quicksort, bench_rbtree_ck, bench_rbtree_delete, bench_cfold, bench_deriv, bench_nqueens, bench_quicksort, bench_rbtree_ck, bench_rbtree_delete,
}; };
use criterion::{ use criterion::{measurement::WallTime, BenchmarkGroup, Criterion, SamplingMode};
criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion, SamplingMode,
};
fn bench_group_wall_time(c: &mut Criterion) { fn bench_group_wall_time(c: &mut Criterion) {
let mut group = c.benchmark_group("bench-group_wall-time"); 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.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<fn(Option<&mut BenchmarkGroup<WallTime>>) -> ()> = vec![ let bench_funcs: Vec<fn(Option<&mut BenchmarkGroup<WallTime>>) -> ()> = vec![
bench_nqueens, // queens 11 bench_nqueens, // queens 11
@ -27,5 +40,32 @@ fn bench_group_wall_time(c: &mut Criterion) {
group.finish(); group.finish();
} }
criterion_group!(benches, bench_group_wall_time); // use short warm up and measurement time on dry run
criterion_main!(benches); 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();
}

View file

@ -21,3 +21,4 @@ serde = { version = "1.0", features = ["derive"] }
serde-xml-rs = "0.4" serde-xml-rs = "0.4"
strip-ansi-escapes = "0.1" strip-ansi-escapes = "0.1"
tempfile = "3.1.0" tempfile = "3.1.0"
rlimit = "0.6.2"

View file

@ -1,5 +1,6 @@
use crate::helpers::{example_file, run_cmd, run_roc}; use crate::helpers::{example_file, run_cmd, run_roc};
use criterion::{black_box, measurement::Measurement, BenchmarkGroup}; use criterion::{black_box, measurement::Measurement, BenchmarkGroup};
use rlimit::{setrlimit, Resource};
use std::path::Path; use std::path::Path;
fn exec_bench_w_input<T: Measurement>( fn exec_bench_w_input<T: Measurement>(
@ -34,11 +35,17 @@ fn check_cmd_output(
executable_filename: &str, executable_filename: &str,
expected_ending: &str, expected_ending: &str,
) { ) {
let out = run_cmd( let cmd_str = file
file.with_file_name(executable_filename).to_str().unwrap(), .with_file_name(executable_filename)
stdin_str, .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) { if !&out.stdout.ends_with(expected_ending) {
panic!( panic!(
@ -55,31 +62,41 @@ fn bench_cmd<T: Measurement>(
executable_filename: &str, executable_filename: &str,
bench_group_opt: Option<&mut BenchmarkGroup<T>>, bench_group_opt: Option<&mut BenchmarkGroup<T>>,
) { ) {
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 { if let Some(bench_group) = bench_group_opt {
bench_group.bench_function(&format!("Benchmarking {:?}", executable_filename), |b| { bench_group.bench_function(&format!("Benchmarking {:?}", executable_filename), |b| {
b.iter(|| { b.iter(|| run_cmd(black_box(&cmd_str), black_box(&[stdin_str]), &[]))
run_cmd(
black_box(file.with_file_name(executable_filename).to_str().unwrap()),
black_box(stdin_str),
&[],
)
})
}); });
} else { } else {
run_cmd( run_cmd(
black_box(file.with_file_name(executable_filename).to_str().unwrap()), 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<T: Measurement>(bench_group_opt: Option<&mut BenchmarkGroup<T>>) { pub fn bench_nqueens<T: Measurement>(bench_group_opt: Option<&mut BenchmarkGroup<T>>) {
exec_bench_w_input( exec_bench_w_input(
&example_file("benchmarks", "NQueens.roc"), &example_file("benchmarks", "NQueens.roc"),
"11", "11",
"nqueens", "nqueens",
"2680\n", "2680\n", //2680-14200
bench_group_opt, bench_group_opt,
); );
} }

View file

@ -1,5 +1,4 @@
extern crate bumpalo; extern crate bumpalo;
extern crate inlinable_string;
extern crate roc_collections; extern crate roc_collections;
extern crate roc_load; extern crate roc_load;
extern crate roc_module; extern crate roc_module;
@ -65,7 +64,7 @@ pub fn run_roc(args: &[&str]) -> Out {
} }
#[allow(dead_code)] #[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); let mut cmd = Command::new(cmd_name);
for arg in args { for arg in args {
@ -81,10 +80,13 @@ pub fn run_cmd(cmd_name: &str, stdin_str: &str, args: &[&str]) -> Out {
{ {
let stdin = child.stdin.as_mut().expect("Failed to open stdin"); let stdin = child.stdin.as_mut().expect("Failed to open stdin");
for stdin_str in stdin_vals {
stdin stdin
.write_all(stdin_str.as_bytes()) .write_all(stdin_str.as_bytes())
.expect("Failed to write to stdin"); .expect("Failed to write to stdin");
} }
}
let output = child let output = child
.wait_with_output() .wait_with_output()
@ -98,7 +100,7 @@ pub fn run_cmd(cmd_name: &str, stdin_str: &str, args: &[&str]) -> Out {
} }
#[allow(dead_code)] #[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. //TODO: figure out if there is a better way to get the valgrind executable.
let mut cmd = Command::new("valgrind"); let mut cmd = Command::new("valgrind");
let named_tempfile = let named_tempfile =
@ -142,10 +144,13 @@ pub fn run_with_valgrind(stdin_str: &str, args: &[&str]) -> (Out, String) {
{ {
let stdin = child.stdin.as_mut().expect("Failed to open stdin"); let stdin = child.stdin.as_mut().expect("Failed to open stdin");
for stdin_str in stdin_vals {
stdin stdin
.write_all(stdin_str.as_bytes()) .write_all(stdin_str.as_bytes())
.expect("Failed to write to stdin"); .expect("Failed to write to stdin");
} }
}
let output = child let output = child
.wait_with_output() .wait_with_output()
@ -228,7 +233,7 @@ pub fn extract_valgrind_errors(xml: &str) -> Result<Vec<ValgrindError>, serde_xm
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn example_dir(dir_name: &str) -> PathBuf { pub fn root_dir() -> PathBuf {
let mut path = env::current_exe().ok().unwrap(); let mut path = env::current_exe().ok().unwrap();
// Get rid of the filename in target/debug/deps/cli_run-99c65e4e9a1fbd06 // 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.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} // Descend into examples/{dir_name}
path.push("examples"); path.push("examples");
path.push(dir_name); path.push(dir_name);
@ -252,7 +264,7 @@ pub fn example_dir(dir_name: &str) -> PathBuf {
#[allow(dead_code)] #[allow(dead_code)]
pub fn example_file(dir_name: &str, file_name: &str) -> PathBuf { 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); path.push(file_name);
@ -261,19 +273,7 @@ pub fn example_file(dir_name: &str, file_name: &str) -> PathBuf {
#[allow(dead_code)] #[allow(dead_code)]
pub fn fixtures_dir(dir_name: &str) -> PathBuf { pub fn fixtures_dir(dir_name: &str) -> PathBuf {
let mut path = env::current_exe().ok().unwrap(); let mut path = root_dir();
// 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();
// Descend into cli/tests/fixtures/{dir_name} // Descend into cli/tests/fixtures/{dir_name}
path.push("cli"); path.push("cli");

View file

@ -65,7 +65,7 @@ pub fn build_file<'a>(
}; };
let loaded = roc_load::file::load_and_monomorphize( let loaded = roc_load::file::load_and_monomorphize(
&arena, arena,
roc_file_path.clone(), roc_file_path.clone(),
stdlib, stdlib,
src_dir.as_path(), src_dir.as_path(),
@ -128,11 +128,11 @@ pub fn build_file<'a>(
let cwd = roc_file_path.parent().unwrap(); let cwd = roc_file_path.parent().unwrap();
let binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows let binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows
let code_gen_timing = program::gen_from_mono_module( let code_gen_timing = program::gen_from_mono_module(
&arena, arena,
loaded, loaded,
&roc_file_path, &roc_file_path,
Triple::host(), Triple::host(),
&app_o_file, app_o_file,
opt_level, opt_level,
emit_debug_info, emit_debug_info,
); );

View file

@ -1,6 +1,9 @@
#[macro_use] #[macro_use]
extern crate clap; extern crate clap;
#[macro_use]
extern crate const_format;
use build::{BuildOutcome, BuiltFile}; use build::{BuildOutcome, BuiltFile};
use bumpalo::Bump; use bumpalo::Bump;
use clap::{App, AppSettings, Arg, ArgMatches}; 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> { pub fn build_app<'a>() -> App<'a> {
let app = App::new("roc") 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) .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(
Arg::with_name(ROC_FILE) Arg::with_name(ROC_FILE)
.help("The .roc file to build") .help("The .roc file to build")
@ -43,7 +47,7 @@ pub fn build_app<'a>() -> App<'a> {
.arg( .arg(
Arg::with_name(FLAG_OPTIMIZE) Arg::with_name(FLAG_OPTIMIZE)
.long(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), .required(false),
) )
.arg( .arg(
@ -60,7 +64,7 @@ pub fn build_app<'a>() -> App<'a> {
) )
) )
.subcommand(App::new(CMD_RUN) .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) .setting(AppSettings::TrailingVarArg)
.arg( .arg(
Arg::with_name(FLAG_OPTIMIZE) Arg::with_name(FLAG_OPTIMIZE)
@ -76,7 +80,7 @@ pub fn build_app<'a>() -> App<'a> {
) )
.arg( .arg(
Arg::with_name(ROC_FILE) 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), .required(true),
) )
.arg( .arg(
@ -94,10 +98,36 @@ pub fn build_app<'a>() -> App<'a> {
.arg(Arg::with_name(DIRECTORY_OR_FILES) .arg(Arg::with_name(DIRECTORY_OR_FILES)
.index(1) .index(1)
.multiple(true) .multiple(true)
.required(true) .required(false)
.help("The directory or files to build documentation for") .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") { if cfg!(feature = "editor") {
@ -151,8 +181,7 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io::
LinkType::Executable LinkType::Executable
}; };
let path = Path::new(filename).canonicalize().unwrap(); let path = Path::new(filename);
let src_dir = path.parent().unwrap().canonicalize().unwrap();
// Spawn the root task // Spawn the root task
let path = path.canonicalize().unwrap_or_else(|err| { 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( let res_binary_path = build_file(
&arena, &arena,
target, target,
@ -215,7 +245,7 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io::
// Forward all the arguments after the .roc file argument // Forward all the arguments after the .roc file argument
// to the new process. This way, you can do things like: // 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`, // ...and have it so that app.roc will receive only `foo`,
// `bar`, and `baz` as its arguments. // `bar`, and `baz` as its arguments.
@ -263,16 +293,16 @@ fn roc_run(cmd: &mut Command) -> io::Result<i32> {
.spawn() .spawn()
.unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err)) .unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err))
.wait() .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 // If you want to know whether there were compilation problems
// via status code, use either `roc build` or `roc check` instead! // via status code, use either `roc build` or `roc check` instead!
match exit_status.code() { match exit_status.code() {
Some(code) => Ok(code), Some(code) => Ok(code),
None => { 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.");
} }
} }
} }

View file

@ -9,6 +9,7 @@ use target_lexicon::Triple;
#[cfg(feature = "llvm")] #[cfg(feature = "llvm")]
use roc_cli::build; use roc_cli::build;
use std::ffi::{OsStr, OsString};
#[cfg(not(feature = "llvm"))] #[cfg(not(feature = "llvm"))]
fn build(_target: &Triple, _matches: &clap::ArgMatches, _config: BuildConfig) -> io::Result<i32> { fn build(_target: &Triple, _matches: &clap::ArgMatches, _config: BuildConfig) -> io::Result<i32> {
@ -19,11 +20,24 @@ fn main() -> io::Result<()> {
let matches = build_app().get_matches(); let matches = build_app().get_matches();
let exit_code = match matches.subcommand_name() { let exit_code = match matches.subcommand_name() {
None => {
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!
build(
&Triple::host(),
&matches,
BuildConfig::BuildAndRun { roc_file_arg_index },
)
}
None => { None => {
launch_editor(&[])?; launch_editor(&[])?;
// rustc couldn't infer the error type here Ok(0)
Result::<i32, io::Error>::Ok(0) }
}
} }
Some(CMD_BUILD) => Ok(build( Some(CMD_BUILD) => Ok(build(
&Triple::host(), &Triple::host(),
@ -31,14 +45,15 @@ fn main() -> io::Result<()> {
BuildConfig::BuildOnly, BuildConfig::BuildOnly,
)?), )?),
Some(CMD_RUN) => { Some(CMD_RUN) => {
let subcmd_matches = matches.subcommand_matches(CMD_RUN).unwrap(); // TODO remove CMD_RUN altogether if it is currently September 2021 or later.
let roc_file_arg_index = subcmd_matches.index_of(ROC_FILE).unwrap() + 1; // Not sure why this +1 is necessary, but it is! 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( Ok(1)
&Triple::host(),
subcmd_matches,
BuildConfig::BuildAndRun { roc_file_arg_index },
)?)
} }
Some(CMD_REPL) => { Some(CMD_REPL) => {
repl::main()?; repl::main()?;
@ -68,19 +83,34 @@ fn main() -> io::Result<()> {
Ok(0) Ok(0)
} }
Some(CMD_DOCS) => { Some(CMD_DOCS) => {
let values = matches let maybe_values = matches
.subcommand_matches(CMD_DOCS) .subcommand_matches(CMD_DOCS)
.unwrap() .unwrap()
.values_of_os(DIRECTORY_OR_FILES) .values_of_os(DIRECTORY_OR_FILES);
.unwrap();
let mut values: Vec<OsString> = Vec::new();
match maybe_values {
None => {
let mut os_string_values: Vec<OsString> = 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(); let mut roc_files = Vec::new();
// Populate roc_files // Populate roc_files
for os_str in values { for os_str in values {
let metadata = fs::metadata(os_str)?; let metadata = fs::metadata(os_str.clone())?;
roc_files_recursive(os_str.as_os_str(), metadata.file_type(), &mut roc_files)?;
roc_files_recursive(os_str, metadata.file_type(), &mut roc_files)?;
} }
docs(roc_files); docs(roc_files);
@ -93,6 +123,26 @@ fn main() -> io::Result<()> {
std::process::exit(exit_code); std::process::exit(exit_code);
} }
fn read_all_roc_files(
dir: &OsString,
mut roc_file_paths: &mut Vec<OsString>,
) -> 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<P: AsRef<Path>>( fn roc_files_recursive<P: AsRef<Path>>(
path: P, path: P,
file_type: FileType, file_type: FileType,

View file

@ -1,17 +1,15 @@
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use libloading::Library; use libloading::Library;
use roc_collections::all::MutMap;
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type}; 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::operator::CalledVia;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::ProcLayout; use roc_mono::ir::ProcLayout;
use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant}; use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant};
use roc_parse::ast::{AssignedField, Expr, StrLiteral}; use roc_parse::ast::{AssignedField, Expr, StrLiteral};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::{Content, FlatType, Subs, Variable}; use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable};
use roc_types::types::RecordField;
struct Env<'a, 'env> { struct Env<'a, 'env> {
arena: &'a Bump, arena: &'a Bump,
@ -153,27 +151,38 @@ fn jit_to_ast_help<'a>(
Layout::Struct(field_layouts) => { Layout::Struct(field_layouts) => {
let ptr_to_ast = |ptr: *const u8| match content { let ptr_to_ast = |ptr: *const u8| match content {
Content::Structure(FlatType::Record(fields, _)) => { Content::Structure(FlatType::Record(fields, _)) => {
Ok(struct_to_ast(env, ptr, field_layouts, fields)) Ok(struct_to_ast(env, ptr, field_layouts, *fields))
}
Content::Structure(FlatType::EmptyRecord) => {
Ok(struct_to_ast(env, ptr, field_layouts, &MutMap::default()))
} }
Content::Structure(FlatType::EmptyRecord) => Ok(struct_to_ast(
env,
ptr,
field_layouts,
RecordFields::empty(),
)),
Content::Structure(FlatType::TagUnion(tags, _)) => { Content::Structure(FlatType::TagUnion(tags, _)) => {
debug_assert_eq!(tags.len(), 1); 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( Ok(single_tag_union_to_ast(
env, env,
ptr, ptr,
field_layouts, field_layouts,
tag_name.clone(), tag_name,
payload_vars, payload_vars,
)) ))
} }
Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => Ok( 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];
),
Ok(single_tag_union_to_ast(
env,
ptr,
field_layouts,
tag_name,
&[],
))
}
Content::Structure(FlatType::Func(_, _, _)) => { Content::Structure(FlatType::Func(_, _, _)) => {
// a function with a struct as the closure environment // a function with a struct as the closure environment
Err(ToAstProblem::FunctionLayout) Err(ToAstProblem::FunctionLayout)
@ -205,8 +214,13 @@ fn jit_to_ast_help<'a>(
Content::Structure(FlatType::TagUnion(tags, _)) => { Content::Structure(FlatType::TagUnion(tags, _)) => {
debug_assert_eq!(union_layouts.len(), tags.len()); debug_assert_eq!(union_layouts.len(), tags.len());
let tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)> = let tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)> = tags
tags.iter().map(|(a, b)| (a.clone(), b.clone())).collect(); .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); 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 => { Builtin::Int16 => {
*(ptr.add(offset as usize) as *const i16) as i64 *(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"), _ => unreachable!("invalid tag id layout"),
}; };
@ -256,7 +275,7 @@ fn jit_to_ast_help<'a>(
let loc_tag_expr = let loc_tag_expr =
&*env.arena.alloc(Located::at_zero(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()); debug_assert_eq!(arg_layouts.len(), variables.len());
@ -289,7 +308,7 @@ fn jit_to_ast_help<'a>(
let loc_tag_expr = let loc_tag_expr =
&*env.arena.alloc(Located::at_zero(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 // because the arg_layouts include the tag ID, it is one longer
debug_assert_eq!( debug_assert_eq!(
@ -319,9 +338,9 @@ fn jit_to_ast_help<'a>(
todo!("print recursive tag unions in the REPL") todo!("print recursive tag unions in the REPL")
} }
Content::Alias(_, _, actual) => { 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), other => unreachable!("Weird content for Union layout: {:?}", other),
} }
@ -333,8 +352,6 @@ fn jit_to_ast_help<'a>(
| Layout::RecursivePointer => { | Layout::RecursivePointer => {
todo!("add support for rendering recursive tag unions in the REPL") 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 { match tag_name {
TagName::Global(_) => Expr::GlobalTag( TagName::Global(_) => Expr::GlobalTag(
env.arena 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( TagName::Private(_) => Expr::PrivateTag(
env.arena 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"), 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) num_to_ast(env, number_literal_to_ast(env.arena, num), content)
} }
Layout::Builtin(Builtin::Usize) => { Layout::Builtin(Builtin::Int8) => {
let num = unsafe { *(ptr as *const usize) }; let num = unsafe { *(ptr as *const i8) };
num_to_ast(env, number_literal_to_ast(env.arena, num), content) 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) 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) => { Layout::Builtin(Builtin::Float64) => {
let num = unsafe { *(ptr as *const f64) }; let num = unsafe { *(ptr as *const f64) };
@ -420,19 +442,20 @@ fn ptr_to_ast<'a>(
} }
Layout::Struct(field_layouts) => match content { Layout::Struct(field_layouts) => match content {
Content::Structure(FlatType::Record(fields, _)) => { 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, _)) => { Content::Structure(FlatType::TagUnion(tags, _)) => {
debug_assert_eq!(tags.len(), 1); 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);
single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), payload_vars) single_tag_union_to_ast(env, ptr, field_layouts, tag_name, payload_vars)
} }
Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => { 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) => { Content::Structure(FlatType::EmptyRecord) => {
struct_to_ast(env, ptr, &[], &MutMap::default()) struct_to_ast(env, ptr, &[], RecordFields::empty())
} }
other => { other => {
unreachable!( unreachable!(
@ -461,9 +484,10 @@ fn list_to_ast<'a>(
Content::Structure(FlatType::Apply(Symbol::LIST_LIST, vars)) => { Content::Structure(FlatType::Apply(Symbol::LIST_LIST, vars)) => {
debug_assert_eq!(vars.len(), 1); 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 => { other => {
unreachable!( unreachable!(
@ -474,14 +498,14 @@ fn list_to_ast<'a>(
}; };
let arena = env.arena; 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; let elem_size = elem_layout.stack_size(env.ptr_bytes) as usize;
for index in 0..len { for index in 0..len {
let offset_bytes = index * elem_size; let offset_bytes = index * elem_size;
let elem_ptr = unsafe { ptr.add(offset_bytes) }; let elem_ptr = unsafe { ptr.add(offset_bytes) };
let loc_expr = &*arena.alloc(Located { 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(), region: Region::zero(),
}); });
@ -500,14 +524,14 @@ fn single_tag_union_to_ast<'a>(
env: &Env<'a, '_>, env: &Env<'a, '_>,
ptr: *const u8, ptr: *const u8,
field_layouts: &'a [Layout<'a>], field_layouts: &'a [Layout<'a>],
tag_name: TagName, tag_name: &TagName,
payload_vars: &[Variable], payload_vars: &[Variable],
) -> Expr<'a> { ) -> Expr<'a> {
debug_assert_eq!(field_layouts.len(), payload_vars.len()); debug_assert_eq!(field_layouts.len(), payload_vars.len());
let arena = env.arena; 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)); let loc_tag_expr = &*arena.alloc(Located::at_zero(tag_expr));
@ -528,14 +552,14 @@ where
{ {
let arena = env.arena; let arena = env.arena;
let subs = env.subs; 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 // We'll advance this as we iterate through the fields
let mut field_ptr = ptr as *const u8; let mut field_ptr = ptr as *const u8;
for (var, layout) in sequence { for (var, layout) in sequence {
let content = subs.get_without_compacting(var).content; let content = subs.get_content_without_compacting(var);
let expr = ptr_to_ast(env, field_ptr, layout, &content); let expr = ptr_to_ast(env, field_ptr, layout, content);
let loc_expr = Located::at_zero(expr); let loc_expr = Located::at_zero(expr);
output.push(&*arena.alloc(loc_expr)); output.push(&*arena.alloc(loc_expr));
@ -551,31 +575,25 @@ fn struct_to_ast<'a>(
env: &Env<'a, '_>, env: &Env<'a, '_>,
ptr: *const u8, ptr: *const u8,
field_layouts: &'a [Layout<'a>], field_layouts: &'a [Layout<'a>],
fields: &MutMap<Lowercase, RecordField<Variable>>, record_fields: RecordFields,
) -> Expr<'a> { ) -> Expr<'a> {
let arena = env.arena; let arena = env.arena;
let subs = env.subs; 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 sorted_fields: Vec<_> = Vec::from_iter_in(
let mut sorted_fields = { record_fields.sorted_iterator(env.subs, Variable::EMPTY_RECORD),
let mut vec = fields env.arena,
.iter() );
.collect::<std::vec::Vec<(&Lowercase, &RecordField<Variable>)>>();
vec.sort_by(|(label1, _), (label2, _)| label1.cmp(label2));
vec
};
if sorted_fields.len() == 1 { if sorted_fields.len() == 1 {
// this is a 1-field wrapper record around another record or 1-tag tag union // 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 { 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(), region: Region::zero(),
}); });
@ -600,10 +618,12 @@ fn struct_to_ast<'a>(
// We'll advance this as we iterate through the fields // We'll advance this as we iterate through the fields
let mut field_ptr = ptr; let mut field_ptr = ptr;
for ((label, field), field_layout) in sorted_fields.iter().zip(field_layouts.iter()) { for ((label, field), field_layout) in sorted_fields.into_iter().zip(field_layouts.iter()) {
let content = subs.get_without_compacting(*field.as_inner()).content; let var = field.into_inner();
let content = subs.get_content_without_compacting(var);
let loc_expr = &*arena.alloc(Located { 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(), 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> { fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a> {
use Content::*; use Content::*;
@ -643,7 +693,11 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
FlatType::Record(fields, _) => { FlatType::Record(fields, _) => {
debug_assert_eq!(fields.len(), 1); 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 { let loc_label = Located {
value: &*arena.alloc_str(label.as_str()), value: &*arena.alloc_str(label.as_str()),
region: Region::zero(), 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 } } }), // and/or records (e.g. { a: { b: { c: True } } }),
// so we need to do this recursively on the field type. // so we need to do this recursively on the field type.
let field_var = *field.as_inner(); 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 { let loc_expr = Located {
value: bool_to_ast(env, value, &field_content), value: bool_to_ast(env, value, field_content),
region: Region::zero(), 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 => { 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 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('@') { let tag_expr = if tag_name.starts_with('@') {
Expr::PrivateTag(arena.alloc_str(tag_name)) Expr::PrivateTag(arena.alloc_str(tag_name))
} else { } 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); debug_assert_eq!(payload_vars.len(), 1);
let var = *payload_vars.iter().next().unwrap(); 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 { let loc_payload = &*arena.alloc(Located {
value: bool_to_ast(env, value, &content), value: bool_to_ast(env, value, content),
region: Region::zero(), 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) Expr::Apply(loc_tag_expr, payload, CalledVia::Space)
} }
FlatType::TagUnion(tags, _) if tags.len() == 2 => { FlatType::TagUnion(tags, _) if tags.len() == 2 => {
let mut tags_iter = tags.iter(); let (tag_name_1, payload_vars_1, tag_name_2, payload_vars_2) =
let (tag_name_1, payload_vars_1) = tags_iter.next().unwrap(); unpack_two_element_tag_union(env.subs, *tags);
let (tag_name_2, payload_vars_2) = tags_iter.next().unwrap();
debug_assert!(payload_vars_1.is_empty()); debug_assert!(payload_vars_1.is_empty());
debug_assert!(payload_vars_2.is_empty()); debug_assert!(payload_vars_2.is_empty());
let tag_name = if value { let tag_name = if value {
max_by_key(tag_name_1, tag_name_2, |n| { 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 { } else {
min_by_key(tag_name_1, tag_name_2, |n| { 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) => { 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 => { other => {
unreachable!("Unexpected FlatType {:?} in bool_to_ast", 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, _) => { FlatType::Record(fields, _) => {
debug_assert_eq!(fields.len(), 1); 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 { let loc_label = Located {
value: &*arena.alloc_str(label.as_str()), value: &*arena.alloc_str(label.as_str()),
region: Region::zero(), 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 } } }), // and/or records (e.g. { a: { b: { c: True } } }),
// so we need to do this recursively on the field type. // so we need to do this recursively on the field type.
let field_var = *field.as_inner(); 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 { let loc_expr = Located {
value: byte_to_ast(env, value, &field_content), value: byte_to_ast(env, value, field_content),
region: Region::zero(), 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 => { 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 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('@') { let tag_expr = if tag_name.starts_with('@') {
Expr::PrivateTag(arena.alloc_str(tag_name)) Expr::PrivateTag(arena.alloc_str(tag_name))
} else { } 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); debug_assert_eq!(payload_vars.len(), 1);
let var = *payload_vars.iter().next().unwrap(); 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 { let loc_payload = &*arena.alloc(Located {
value: byte_to_ast(env, value, &content), value: byte_to_ast(env, value, content),
region: Region::zero(), 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 // anything with fewer tags is not a byte
debug_assert!(tags.len() > 2); debug_assert!(tags.len() > 2);
let tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)> = let tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)> = tags
tags.iter().map(|(a, b)| (a.clone(), b.clone())).collect(); .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); 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) => { 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 => { other => {
unreachable!("Unexpected FlatType {:?} in bool_to_ast", 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. // Its type signature will tell us that.
debug_assert_eq!(fields.len(), 1); 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 { let loc_label = Located {
value: &*arena.alloc_str(label.as_str()), value: &*arena.alloc_str(label.as_str()),
region: Region::zero(), 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 } } }), // and/or records (e.g. { a: { b: { c: 5 } } }),
// so we need to do this recursively on the field type. // so we need to do this recursively on the field type.
let field_var = *field.as_inner(); 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 { 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(), 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. // This was a single-tag union that got unwrapped at runtime.
debug_assert_eq!(tags.len(), 1); 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 // If this tag union represents a number, skip right to
// returning tis as an Expr::Num // 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 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('@') { let tag_expr = if tag_name.starts_with('@') {
Expr::PrivateTag(arena.alloc_str(tag_name)) Expr::PrivateTag(arena.alloc_str(tag_name))
} else { } 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); debug_assert_eq!(payload_vars.len(), 1);
let var = *payload_vars.iter().next().unwrap(); 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 { 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(), region: Region::zero(),
}); });
@ -950,9 +1013,9 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E
} }
} }
Alias(_, _, var) => { 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 => { other => {
panic!("Unexpected FlatType {:?} in num_to_ast", other); panic!("Unexpected FlatType {:?} in num_to_ast", other);

View file

@ -135,10 +135,6 @@ pub fn gen_and_eval<'a>(
&context, "", &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 // mark our zig-defined builtins as internal
for function in FunctionIterator::from_module(module) { for function in FunctionIterator::from_module(module) {
let name = function.get_name().to_str().unwrap(); 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. // pretty-print the expr type string for later.
name_all_type_vars(main_fn_var, &mut subs); name_all_type_vars(main_fn_var, &mut subs);
let content = subs.get(main_fn_var).content; let content = subs.get_content_without_compacting(main_fn_var);
let expr_type_str = content_to_string(content.clone(), &subs, home, &interns); let expr_type_str = content_to_string(content, &subs, home, &interns);
let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
Some(layout) => *layout, Some(layout) => *layout,
@ -183,11 +179,15 @@ pub fn gen_and_eval<'a>(
interns, interns,
module, module,
ptr_bytes, ptr_bytes,
leak: false, is_gen_test: false,
// important! we don't want any procedures to get the C calling convention // important! we don't want any procedures to get the C calling convention
exposed_to_host: MutSet::default(), 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( let (main_fn_name, main_fn) = roc_gen_llvm::llvm::build::build_procedures_return_main(
&env, &env,
opt_level, 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"); .expect("Error loading compiled dylib for test");
let res_answer = unsafe { let res_answer = unsafe {
eval::jit_to_ast( eval::jit_to_ast(
@ -227,7 +227,7 @@ pub fn gen_and_eval<'a>(
lib, lib,
main_fn_name, main_fn_name,
main_fn_layout, main_fn_layout,
&content, content,
&env.interns, &env.interns,
home, home,
&subs, &subs,

View file

@ -1,8 +1,7 @@
// #[macro_use] #[macro_use]
extern crate pretty_assertions; extern crate pretty_assertions;
extern crate bumpalo; extern crate bumpalo;
extern crate inlinable_string;
extern crate roc_collections; extern crate roc_collections;
extern crate roc_load; extern crate roc_load;
extern crate roc_module; extern crate roc_module;
@ -10,12 +9,15 @@ extern crate roc_module;
#[cfg(test)] #[cfg(test)]
mod cli_run { mod cli_run {
use cli_utils::helpers::{ use cli_utils::helpers::{
example_file, extract_valgrind_errors, fixture_file, run_cmd, run_roc, run_with_valgrind, example_file, examples_dir, extract_valgrind_errors, fixture_file, run_cmd, run_roc,
ValgrindError, ValgrindErrorXWhat, run_with_valgrind, ValgrindError, ValgrindErrorXWhat,
}; };
use serial_test::serial; use serial_test::serial;
use std::path::Path; use std::path::Path;
#[cfg(not(debug_assertions))]
use roc_collections::all::MutMap;
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
const ALLOW_VALGRIND: bool = true; const ALLOW_VALGRIND: bool = true;
@ -25,26 +27,18 @@ mod cli_run {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
const ALLOW_VALGRIND: bool = false; const ALLOW_VALGRIND: bool = false;
fn check_output( #[derive(Debug, PartialEq, Eq)]
file: &Path, struct Example<'a> {
executable_filename: &str, filename: &'a str,
flags: &[&str], executable_filename: &'a str,
expected_ending: &str, stdin: &'a [&'a str],
expected_ending: &'a str,
use_valgrind: bool, use_valgrind: bool,
) {
check_output_with_stdin(
file,
"",
executable_filename,
flags,
expected_ending,
use_valgrind,
)
} }
fn check_output_with_stdin( fn check_output_with_stdin(
file: &Path, file: &Path,
stdin_str: &str, stdin: &[&str],
executable_filename: &str, executable_filename: &str,
flags: &[&str], flags: &[&str],
expected_ending: &str, expected_ending: &str,
@ -59,7 +53,7 @@ mod cli_run {
let out = if use_valgrind && ALLOW_VALGRIND { let out = if use_valgrind && ALLOW_VALGRIND {
let (valgrind_out, raw_xml) = run_with_valgrind( let (valgrind_out, raw_xml) = run_with_valgrind(
stdin_str, stdin,
&[file.with_file_name(executable_filename).to_str().unwrap()], &[file.with_file_name(executable_filename).to_str().unwrap()],
); );
@ -101,7 +95,7 @@ mod cli_run {
} else { } else {
run_cmd( run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(), file.with_file_name(executable_filename).to_str().unwrap(),
stdin_str, stdin,
&[], &[],
) )
}; };
@ -114,182 +108,344 @@ mod cli_run {
assert!(out.status.success()); assert!(out.status.success());
} }
/// 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] #[test]
#[serial(hello_world)] fn $test_name() {
fn run_hello_world() { let dir_name = $name;
check_output( let example = $example;
&example_file("hello-world", "Hello.roc"), let file_name = example_file(dir_name, example.filename);
"hello-world",
&[],
"Hello, World!\n",
true,
);
}
#[test] // Check with and without optimizations
#[serial(hello_world)] check_output_with_stdin(
fn run_hello_world_optimized() { &file_name,
check_output( example.stdin,
&example_file("hello-world", "Hello.roc"), example.executable_filename,
"hello-world",
&[], &[],
"Hello, World!\n", example.expected_ending,
true, example.use_valgrind,
); );
}
#[test] check_output_with_stdin(
#[serial(quicksort)] &file_name,
fn run_quicksort_not_optimized() { example.stdin,
check_output( example.executable_filename,
&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,
);
}
#[test]
#[serial(quicksort)]
fn run_quicksort_optimized() {
check_output(
&example_file("quicksort", "Quicksort.roc"),
"quicksort",
&["--optimize"], &["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", example.expected_ending,
true, example.use_valgrind,
); );
} }
)*
#[test] #[test]
#[serial(quicksort)] #[cfg(not(debug_assertions))]
fn run_quicksort_optimized_valgrind() { fn all_examples_have_tests() {
check_output( let mut all_examples: MutMap<&str, Example<'_>> = MutMap::default();
&example_file("quicksort", "Quicksort.roc"),
"quicksort", $(
all_examples.insert($name, $example);
)*
check_for_tests("../examples", &mut all_examples);
}
}
}
// 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,
// },
}
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"], &["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", benchmark.expected_ending,
true, benchmark.use_valgrind,
); );
} }
)*
#[test] #[test]
#[serial(nqueens)] #[cfg(not(debug_assertions))]
fn run_nqueens_not_optimized() { fn all_benchmarks_have_tests() {
check_output_with_stdin( let mut all_benchmarks: MutMap<&str, Example<'_>> = MutMap::default();
&example_file("benchmarks", "NQueens.roc"),
"6", $(
"nqueens", let benchmark = $benchmark;
&[],
"4\n", all_benchmarks.insert(benchmark.filename, benchmark);
true, )*
);
check_for_benchmarks("../examples/benchmarks", &mut all_benchmarks);
}
}
} }
#[test] benchmarks! {
#[serial(cfold)] nqueens => Example {
fn run_cfold_not_optimized() { filename: "NQueens.roc",
check_output_with_stdin( executable_filename: "nqueens",
&example_file("benchmarks", "CFold.roc"), stdin: &["6"],
"3", expected_ending: "4\n",
"cfold", use_valgrind: true,
&[], },
"11 & 11\n", cfold => Example {
true, 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] #[cfg(not(debug_assertions))]
#[serial(deriv)] fn check_for_tests(examples_dir: &str, all_examples: &mut MutMap<&str, Example<'_>>) {
fn run_deriv_not_optimized() { let entries = std::fs::read_dir(examples_dir).unwrap_or_else(|err| {
check_output_with_stdin( panic!(
&example_file("benchmarks", "Deriv.roc"), "Error trying to read {} as an examples directory: {}",
"2", examples_dir, err
"deriv",
&[],
"1 count: 6\n2 count: 22\n",
true,
); );
});
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);
});
}
}
} }
#[test] assert_eq!(all_examples, &mut MutMap::default());
#[serial(deriv)]
fn run_rbtree_insert_not_optimized() {
check_output(
&example_file("benchmarks", "RBTreeInsert.roc"),
"rbtree-insert",
&[],
"Node Black 0 {} Empty Empty\n",
true,
);
} }
#[test] #[cfg(not(debug_assertions))]
#[serial(deriv)] fn check_for_benchmarks(benchmarks_dir: &str, all_benchmarks: &mut MutMap<&str, Example<'_>>) {
fn run_rbtree_delete_not_optimized() { use std::ffi::OsStr;
check_output_with_stdin( use std::fs::File;
&example_file("benchmarks", "RBTreeDel.roc"), use std::io::Read;
"420",
"rbtree-del", let entries = std::fs::read_dir(benchmarks_dir).unwrap_or_else(|err| {
&[], panic!(
"30\n", "Error trying to read {} as a benchmark directory: {}",
true, benchmarks_dir, err
); );
});
for entry in entries {
let entry = entry.unwrap();
let path = entry.path();
if let Some("roc") = path.extension().and_then(OsStr::to_str) {
let benchmark_file_name = entry.file_name().into_string().unwrap();
// 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();
file.read_exact(buf).unwrap();
// 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] assert_eq!(all_benchmarks, &mut MutMap::default());
#[serial(astar)]
fn run_astar_optimized_1() {
check_output(
&example_file("benchmarks", "TestAStar.roc"),
"test-astar",
&[],
"True\n",
false,
);
} }
#[test]
#[serial(base64)]
fn base64() {
check_output(
&example_file("benchmarks", "TestBase64.roc"),
"test-base64",
&[],
"encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n",
true,
);
}
#[test]
#[serial(closure)]
fn closure() {
check_output(
&example_file("benchmarks", "Closure.roc"),
"closure",
&[],
"",
true,
);
}
// #[test]
// #[serial(effect)]
// fn run_effect_unoptimized() {
// check_output(
// &example_file("effect", "Main.roc"),
// &[],
// "I am Dep2.str2\n",
// true,
// );
// }
#[test] #[test]
#[serial(multi_dep_str)] #[serial(multi_dep_str)]
fn run_multi_dep_str_unoptimized() { fn run_multi_dep_str_unoptimized() {
check_output( check_output_with_stdin(
&fixture_file("multi-dep-str", "Main.roc"), &fixture_file("multi-dep-str", "Main.roc"),
&[],
"multi-dep-str", "multi-dep-str",
&[], &[],
"I am Dep2.str2\n", "I am Dep2.str2\n",
@ -300,8 +456,9 @@ mod cli_run {
#[test] #[test]
#[serial(multi_dep_str)] #[serial(multi_dep_str)]
fn run_multi_dep_str_optimized() { fn run_multi_dep_str_optimized() {
check_output( check_output_with_stdin(
&fixture_file("multi-dep-str", "Main.roc"), &fixture_file("multi-dep-str", "Main.roc"),
&[],
"multi-dep-str", "multi-dep-str",
&["--optimize"], &["--optimize"],
"I am Dep2.str2\n", "I am Dep2.str2\n",
@ -312,8 +469,9 @@ mod cli_run {
#[test] #[test]
#[serial(multi_dep_thunk)] #[serial(multi_dep_thunk)]
fn run_multi_dep_thunk_unoptimized() { fn run_multi_dep_thunk_unoptimized() {
check_output( check_output_with_stdin(
&fixture_file("multi-dep-thunk", "Main.roc"), &fixture_file("multi-dep-thunk", "Main.roc"),
&[],
"multi-dep-thunk", "multi-dep-thunk",
&[], &[],
"I am Dep2.value2\n", "I am Dep2.value2\n",
@ -324,8 +482,9 @@ mod cli_run {
#[test] #[test]
#[serial(multi_dep_thunk)] #[serial(multi_dep_thunk)]
fn run_multi_dep_thunk_optimized() { fn run_multi_dep_thunk_optimized() {
check_output( check_output_with_stdin(
&fixture_file("multi-dep-thunk", "Main.roc"), &fixture_file("multi-dep-thunk", "Main.roc"),
&[],
"multi-dep-thunk", "multi-dep-thunk",
&["--optimize"], &["--optimize"],
"I am Dep2.value2\n", "I am Dep2.value2\n",

View file

@ -40,6 +40,13 @@ export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
free(@alignCast(16, @ptrCast([*]u8, c_ptr))); 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 RocCallResult = extern struct { flag: usize, content: RocStr };
const Unit = extern struct {}; const Unit = extern struct {};

View file

@ -40,6 +40,13 @@ export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
free(@alignCast(16, @ptrCast([*]u8, c_ptr))); 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 RocCallResult = extern struct { flag: usize, content: RocStr };
const Unit = extern struct {}; const Unit = extern struct {};

View file

@ -56,7 +56,7 @@ fn find_zig_str_path() -> PathBuf {
} }
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
fn build_zig_host( pub fn build_zig_host(
env_path: &str, env_path: &str,
env_home: &str, env_home: &str,
emit_bin: &str, emit_bin: &str,
@ -86,7 +86,7 @@ fn build_zig_host(
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
fn build_zig_host( pub fn build_zig_host(
env_path: &str, env_path: &str,
env_home: &str, env_home: &str,
emit_bin: &str, emit_bin: &str,
@ -140,7 +140,7 @@ fn build_zig_host(
.args(&[ .args(&[
"build-obj", "build-obj",
zig_host_src, zig_host_src,
&emit_bin, emit_bin,
"--pkg-begin", "--pkg-begin",
"str", "str",
zig_str_path, 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<const N: usize>(segments: [&str; N]) -> Option<PathBuf> {
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( fn link_linux(
target: &Triple, target: &Triple,
output_path: PathBuf, output_path: PathBuf,
input_paths: &[&str], input_paths: &[&str],
link_type: LinkType, link_type: LinkType,
) -> io::Result<(Child, PathBuf)> { ) -> io::Result<(Child, PathBuf)> {
let usr_lib_path = Path::new("/usr/lib").to_path_buf(); let architecture = format!("{}-linux-gnu", target.architecture);
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 libcrt_path = if usr_lib_gnu_path.exists() { let libcrt_path = library_path(["/usr", "lib", &architecture])
&usr_lib_gnu_path .or_else(|| library_path(["/usr", "lib"]))
} else { .or_else(|| library_path([&nixos_path()]))
&usr_lib_path .unwrap();
};
let libgcc_name = "libgcc_s.so.1"; let libgcc_name = "libgcc_s.so.1";
let libgcc_path = if lib_gnu_path.join(libgcc_name).exists() { let libgcc_path = library_path(["/lib", &architecture, libgcc_name])
lib_gnu_path.join(libgcc_name) .or_else(|| library_path(["/usr", "lib", &architecture, libgcc_name]))
} else if usr_lib_gnu_path.join(libgcc_name).exists() { .or_else(|| library_path(["/usr", "lib", libgcc_name]))
usr_lib_gnu_path.join(libgcc_name) .or_else(|| library_path([&nixos_path(), libgcc_name]))
} else { .unwrap();
usr_lib_path.join(libgcc_name)
};
let ld_linux = match target.architecture { let ld_linux = match target.architecture {
Architecture::X86_64 => "/lib64/ld-linux-x86-64.so.2", Architecture::X86_64 => library_path(["/lib64", "ld-linux-x86-64.so.2"])
Architecture::Aarch64(_) => "/lib/ld-linux-aarch64.so.1", .or_else(|| library_path([&nixos_path(), "ld-linux-x86-64.so.2"])),
Architecture::Aarch64(_) => library_path(["/lib", "ld-linux-aarch64.so.1"]),
_ => panic!( _ => panic!(
"TODO gracefully handle unsupported linux architecture: {:?}", "TODO gracefully handle unsupported linux architecture: {:?}",
target.architecture target.architecture
), ),
}; };
let ld_linux = ld_linux.unwrap();
let ld_linux = ld_linux.to_str().unwrap();
let mut soname; let mut soname;
let (base_args, output_path) = match link_type { let (base_args, output_path) = match link_type {
@ -333,7 +355,7 @@ fn link_linux(
output_path, output_path,
), ),
LinkType::Dylib => { 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? // 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 // 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", "-lrt",
"-lutil", "-lutil",
"-lc_nonshared", "-lc_nonshared",
"-lc++",
"-lc++abi",
"-lunwind",
libgcc_path.to_str().unwrap(), libgcc_path.to_str().unwrap(),
// Output // Output
"-o", "-o",
@ -432,7 +451,7 @@ fn link_macos(
// This path only exists on macOS Big Sur, and it causes ld errors // 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 // on Catalina if it's specified with -L, so we replace it with a
// redundant -lSystem if the directory isn't there. // 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() { let big_sur_fix = if Path::new(big_sur_path).exists() {
format!("-L{}", big_sur_path) format!("-L{}", big_sur_path)
} else { } else {
@ -466,9 +485,6 @@ fn link_macos(
// "-lrt", // TODO shouldn't we need this? // "-lrt", // TODO shouldn't we need this?
// "-lc_nonshared", // 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 // "-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 // "-framework", // Uncomment this line & the following ro run the `rand` crate in examples/cli
// "Security", // "Security",
// Output // Output
@ -496,7 +512,7 @@ pub fn module_to_dylib(
app_o_file.set_file_name("app.o"); 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 reloc = RelocMode::PIC;
let model = CodeModel::Default; let model = CodeModel::Default;
let target_machine = let target_machine =

View file

@ -16,6 +16,10 @@ pub struct CodeGenTiming {
pub emit_o_file: Duration, 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 how should imported modules factor into this? What if those use builtins too?
// TODO this should probably use more helper functions // TODO this should probably use more helper functions
// TODO make this polymorphic in the llvm functions so it can be reused for another backend. // 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") if name.starts_with("roc_builtins.dict")
|| name.starts_with("dict.RocDict")
|| name.starts_with("roc_builtins.list") || name.starts_with("roc_builtins.list")
|| name.starts_with("roc_builtins.dec")
|| name.starts_with("list.RocList") || name.starts_with("list.RocList")
|| name.starts_with("dict.RocDict")
{ {
function.add_attribute(AttributeLoc::Function, enum_attr); 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 // Compile and add all the Procs before adding main
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let env = roc_gen_llvm::llvm::build::Env { let env = roc_gen_llvm::llvm::build::Env {
arena: &arena, arena,
builder: &builder, builder: &builder,
dibuilder: &dibuilder, dibuilder: &dibuilder,
compile_unit: &compile_unit, compile_unit: &compile_unit,
@ -139,7 +144,9 @@ pub fn gen_from_mono_module(
interns: loaded.interns, interns: loaded.interns,
module, module,
ptr_bytes, 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(), exposed_to_host: loaded.exposed_to_host.keys().copied().collect(),
}; };
@ -153,6 +160,9 @@ pub fn gen_from_mono_module(
env.dibuilder.finalize(); 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: // Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr(); // env.module.print_to_stderr();
@ -194,7 +204,6 @@ pub fn gen_from_mono_module(
// run the debugir https://github.com/vaivaswatha/debugir tool // run the debugir https://github.com/vaivaswatha/debugir tool
match Command::new("debugir") match Command::new("debugir")
.env_clear()
.args(&["-instnamer", app_ll_file.to_str().unwrap()]) .args(&["-instnamer", app_ll_file.to_str().unwrap()])
.output() .output()
{ {
@ -212,7 +221,6 @@ pub fn gen_from_mono_module(
// assemble the .ll into a .bc // assemble the .ll into a .bc
let _ = Command::new("llvm-as") let _ = Command::new("llvm-as")
.env_clear()
.args(&[ .args(&[
app_ll_dbg_file.to_str().unwrap(), app_ll_dbg_file.to_str().unwrap(),
"-o", "-o",
@ -221,18 +229,26 @@ pub fn gen_from_mono_module(
.output() .output()
.unwrap(); .unwrap();
// write the .o file. Note that this builds the .o for the local machine, let llc_args = &[
// and ignores the `target_machine` entirely.
let _ = Command::new("llc-12")
.env_clear()
.args(&[
"-filetype=obj", "-filetype=obj",
app_bc_file.to_str().unwrap(), app_bc_file.to_str().unwrap(),
"-o", "-o",
app_o_file.to_str().unwrap(), 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.
//
// different systems name this executable differently, so we shotgun for
// the most common ones and then give up.
let _: Result<std::process::Output, std::io::Error> =
Command::new(format!("llc-{}", LLVM_VERSION))
.args(llc_args)
.output() .output()
.unwrap(); .or_else(|_| Command::new("llc").args(llc_args).output())
.map_err(|_| {
panic!("We couldn't find llc-{} on your machine!", LLVM_VERSION);
});
} else { } else {
// Emit the .o file // 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::target_machine(&target, convert_opt_level(opt_level), reloc, model).unwrap();
target_machine 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"); .expect("Writing .o file failed");
} }

View file

@ -27,7 +27,6 @@ pub fn build(b: *Builder) void {
llvm_obj.strip = true; llvm_obj.strip = true;
llvm_obj.emit_llvm_ir = true; llvm_obj.emit_llvm_ir = true;
llvm_obj.emit_bin = false; llvm_obj.emit_bin = false;
llvm_obj.bundle_compiler_rt = true;
const ir = b.step("ir", "Build LLVM ir"); const ir = b.step("ir", "Build LLVM ir");
ir.dependOn(&llvm_obj.step); ir.dependOn(&llvm_obj.step);

View file

@ -1,16 +1,19 @@
const std = @import("std"); const std = @import("std");
const str = @import("str.zig"); const str = @import("str.zig");
const utils = @import("utils.zig");
const math = std.math; const math = std.math;
const always_inline = std.builtin.CallOptions.Modifier.always_inline;
const RocStr = str.RocStr; const RocStr = str.RocStr;
const WithOverflow = utils.WithOverflow;
pub const RocDec = struct { pub const RocDec = extern struct {
num: i128, num: i128,
pub const decimal_places: u5 = 18; pub const decimal_places: u5 = 18;
pub const whole_number_places: u5 = 21; pub const whole_number_places: u5 = 21;
const max_digits: u6 = 39; 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 min: RocDec = .{ .num = math.minInt(i128) };
pub const max: RocDec = .{ .num = math.maxInt(i128) }; pub const max: RocDec = .{ .num = math.maxInt(i128) };
@ -22,6 +25,23 @@ pub const RocDec = struct {
return .{ .num = num * one_point_zero_i128 }; 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 { pub fn fromStr(roc_str: RocStr) ?RocDec {
if (roc_str.isEmpty()) { if (roc_str.isEmpty()) {
return null; return null;
@ -57,7 +77,7 @@ pub const RocDec = struct {
var after_str_len = (length - 1) - pi; var after_str_len = (length - 1) - pi;
if (after_str_len > decimal_places) { 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; var diff_decimal_places = decimal_places - after_str_len;
@ -74,37 +94,38 @@ pub const RocDec = struct {
var result: i128 = undefined; var result: i128 = undefined;
var overflowed = @mulWithOverflow(i128, before, one_point_zero_i128, &result); var overflowed = @mulWithOverflow(i128, before, one_point_zero_i128, &result);
if (overflowed) { if (overflowed) {
std.debug.panic("TODO runtime exception for overflow!", .{}); @panic("TODO runtime exception for overflow!");
} }
before_val_i128 = result; before_val_i128 = result;
} }
var dec: ?RocDec = null; const dec: RocDec = blk: {
if (before_val_i128) |before| { if (before_val_i128) |before| {
if (after_val_i128) |after| { if (after_val_i128) |after| {
var result: i128 = undefined; var result: i128 = undefined;
var overflowed = @addWithOverflow(i128, before, after, &result); var overflowed = @addWithOverflow(i128, before, after, &result);
if (overflowed) { if (overflowed) {
std.debug.panic("TODO runtime exception for overflow!", .{}); @panic("TODO runtime exception for overflow!");
} }
dec = .{ .num = result }; break :blk .{ .num = result };
} else { } else {
dec = .{ .num = before }; break :blk .{ .num = before };
} }
} else if (after_val_i128) |after| { } else if (after_val_i128) |after| {
dec = .{ .num = after }; break :blk .{ .num = after };
} else {
return null;
} }
};
if (dec) |d| {
if (is_negative) { if (is_negative) {
dec = d.negate(); return dec.negate();
} } else {
}
return dec; return dec;
} }
}
fn isDigit(c: u8) bool { inline fn isDigit(c: u8) bool {
return (c -% 48) <= 9; return (c -% 48) <= 9;
} }
@ -114,80 +135,84 @@ pub const RocDec = struct {
return RocStr.init("0.0", 3); return RocStr.init("0.0", 3);
} }
// Check if this Dec is negative, and if so convert to positive const num = self.num;
// We will handle adding the '-' later const is_negative = num < 0;
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;
// Format the backing i128 into an array of digits (u8s) // Format the backing i128 into an array of digit (ascii) characters (u8s)
var digit_bytes: [max_digits + 1]u8 = undefined; var digit_bytes_storage: [max_digits + 1]u8 = undefined;
var num_digits = std.fmt.formatIntBuf(digit_bytes[0..], num, 10, false, .{}); 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 // Get the slice for before the decimal point
var before_digits_slice: []const u8 = undefined;
var before_digits_offset: usize = 0; var before_digits_offset: usize = 0;
var before_digits_adjust: u6 = 0;
if (num_digits > decimal_places) { 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_offset = num_digits - decimal_places;
before_digits_slice = digit_bytes[0..before_digits_offset];
for (digit_bytes[0..before_digits_offset]) |c| {
str_bytes[position] = c;
position += 1;
}
} else { } else {
before_digits_adjust = @intCast(u6, math.absInt(@intCast(i7, num_digits) - decimal_places) catch { // otherwise there are no actual digits before the decimal point
std.debug.panic("TODO runtime exception for overflow when getting abs", .{}); // but we format it with a '0'
}); str_bytes[position] = '0';
before_digits_slice = "0"; position += 1;
} }
// Figure out how many trailing zeros there are // we've done everything before the decimal point, so now we can put the decimal point in
// I tried to use https://ziglang.org/documentation/0.8.0/#ctz and it mostly worked, str_bytes[position] = '.';
// but was giving seemingly incorrect values for certain numbers. So instead we use position += 1;
// 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;
}
index -= 1;
}
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 // 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 // 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_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 i: usize = 0;
var after_digits_slice: []const u8 = undefined; while (i < after_zeros_num) : (i += 1) {
if ((num_digits - before_digits_offset) == trailing_zeros) { str_bytes[position] = '0';
after_digits_slice = "0"; position += 1;
} else {
after_digits_slice = digit_bytes[before_digits_offset .. num_digits - trailing_zeros];
} }
// Get the slice for the sign // otherwise append the decimal digits except the trailing zeros
const sign_slice: []const u8 = if (is_negative) "-" else leading_zeros[0..0]; for (digit_bytes[before_digits_offset .. num_digits - trailing_zeros]) |c| {
str_bytes[position] = c;
position += 1;
}
}
// Hardcode adding a `1` for the '.' character return RocStr.init(&str_bytes, position);
const str_len: usize = sign_slice.len + before_digits_slice.len + 1 + after_zeros_slice.len + after_digits_slice.len; }
// Join the slices together pub fn eq(self: RocDec, other: RocDec) bool {
// We do `max_digits + 2` here because we need to account for a possible sign ('-') and the dot ('.'). return self.num == other.num;
// 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", .{});
};
return RocStr.init(&str_bytes, str_len); pub fn neq(self: RocDec, other: RocDec) bool {
return self.num != other.num;
} }
pub fn negate(self: RocDec) ?RocDec { pub fn negate(self: RocDec) ?RocDec {
@ -195,29 +220,41 @@ pub const RocDec = struct {
return if (negated) |n| .{ .num = n } else null; 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; var answer: i128 = undefined;
const overflowed = @addWithOverflow(i128, self.num, other.num, &answer); const overflowed = @addWithOverflow(i128, self.num, other.num, &answer);
if (!overflowed) { return .{ .value = RocDec{ .num = answer }, .has_overflowed = overflowed };
return RocDec{ .num = answer }; }
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 { } 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; var answer: i128 = undefined;
const overflowed = @subWithOverflow(i128, self.num, other.num, &answer); const overflowed = @subWithOverflow(i128, self.num, other.num, &answer);
if (!overflowed) { return .{ .value = RocDec{ .num = answer }, .has_overflowed = overflowed };
return RocDec{ .num = answer }; }
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 { } 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 self_i128 = self.num;
const other_i128 = other.num; const other_i128 = other.num;
// const answer = 0; //self_i256 * other_i256; // 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 { const self_u128 = @intCast(u128, math.absInt(self_i128) catch {
if (other_i128 == 0) { if (other_i128 == 0) {
return .{ .num = 0 }; return .{ .value = RocDec{ .num = 0 }, .has_overflowed = false };
} else if (other_i128 == RocDec.one_point_zero.num) { } else if (other_i128 == RocDec.one_point_zero.num) {
return self; return .{ .value = self, .has_overflowed = false };
} else { } 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 { const other_u128 = @intCast(u128, math.absInt(other_i128) catch {
if (self_i128 == 0) { if (self_i128 == 0) {
return .{ .num = 0 }; return .{ .value = RocDec{ .num = 0 }, .has_overflowed = false };
} else if (self_i128 == RocDec.one_point_zero.num) { } else if (self_i128 == RocDec.one_point_zero.num) {
return other; return .{ .value = other, .has_overflowed = false };
} else { } 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); const unsigned_answer: i128 = mul_and_decimalize(self_u128, other_u128);
if (is_answer_negative) { if (is_answer_negative) {
return .{ .num = -unsigned_answer }; return .{ .value = RocDec{ .num = -unsigned_answer }, .has_overflowed = false };
} else { } 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 // (n / 0) is an error
if (denominator_i128 == 0) { 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 // 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) { if (denominator_i128 == one_point_zero_i128) {
return self; return self;
} else { } 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); const numerator_u128 = @intCast(u128, numerator_abs_i128);
@ -305,7 +354,7 @@ pub const RocDec = struct {
if (numerator_i128 == one_point_zero_i128) { if (numerator_i128 == one_point_zero_i128) {
return other; return other;
} else { } 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); 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)) { if (answer.hi == 0 and answer.lo <= math.maxInt(i128)) {
unsigned_answer = @intCast(i128, answer.lo); unsigned_answer = @intCast(i128, answer.lo);
} else { } 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 }; 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 { const U256 = struct {
hi: u128, hi: u128,
lo: 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); overflowed = overflowed or @addWithOverflow(u128, d, c_carry4, &d);
if (overflowed) { if (overflowed) {
std.debug.panic("TODO runtime exception for overflow!", .{}); @panic("TODO runtime exception for overflow!");
} }
// Final 512bit value is d, c, b, a // Final 512bit value is d, c, b, a
@ -652,6 +723,11 @@ test "fromU64" {
try expectEqual(RocDec{ .num = 25000000000000000000 }, dec); try expectEqual(RocDec{ .num = 25000000000000000000 }, dec);
} }
test "fromF64" {
var dec = RocDec.fromF64(25.5);
try expectEqual(RocDec{ .num = 25500000000000000000 }, dec.?);
}
test "fromStr: empty" { test "fromStr: empty" {
var roc_str = RocStr.init("", 0); var roc_str = RocStr.init("", 0);
var dec = RocDec.fromStr(roc_str); 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()); 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" { test "toStr: 0" {
var dec: RocDec = .{ .num = 0 }; var dec: RocDec = .{ .num = 0 };
var res_roc_str = dec.toStr(); var res_roc_str = dec.toStr();
@ -953,3 +1049,37 @@ test "div: 10 / 3" {
try expectEqual(res, numer.div(denom)); 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;
}

View file

@ -3,8 +3,6 @@ const utils = @import("utils.zig");
const RocResult = utils.RocResult; const RocResult = utils.RocResult;
const mem = std.mem; const mem = std.mem;
const TAG_WIDTH = 8;
const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool; const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool;
const CompareFn = fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) u8; const CompareFn = fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) u8;
const Opaque = ?[*]u8; const Opaque = ?[*]u8;
@ -735,6 +733,30 @@ pub fn listAppend(list: RocList, alignment: u32, element: Opaque, element_width:
return output; 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( pub fn listSwap(
list: RocList, list: RocList,
alignment: u32, alignment: u32,

View file

@ -5,6 +5,17 @@ const testing = std.testing;
// Dec Module // Dec Module
const dec = @import("dec.zig"); 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 // List Module
const list = @import("list.zig"); const list = @import("list.zig");
@ -22,6 +33,7 @@ comptime {
exportListFn(list.listContains, "contains"); exportListFn(list.listContains, "contains");
exportListFn(list.listRepeat, "repeat"); exportListFn(list.listRepeat, "repeat");
exportListFn(list.listAppend, "append"); exportListFn(list.listAppend, "append");
exportListFn(list.listPrepend, "prepend");
exportListFn(list.listSingle, "single"); exportListFn(list.listSingle, "single");
exportListFn(list.listJoin, "join"); exportListFn(list.listJoin, "join");
exportListFn(list.listRange, "range"); exportListFn(list.listRange, "range");
@ -67,6 +79,9 @@ comptime {
exportNumFn(num.powInt, "pow_int"); exportNumFn(num.powInt, "pow_int");
exportNumFn(num.acos, "acos"); exportNumFn(num.acos, "acos");
exportNumFn(num.asin, "asin"); exportNumFn(num.asin, "asin");
exportNumFn(num.bytesToU16C, "bytes_to_u16");
exportNumFn(num.bytesToU32C, "bytes_to_u32");
exportNumFn(num.round, "round");
} }
// Str Module // Str Module
@ -77,7 +92,7 @@ comptime {
exportStrFn(str.countSegments, "count_segments"); exportStrFn(str.countSegments, "count_segments");
exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters"); exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters");
exportStrFn(str.startsWith, "starts_with"); 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.endsWith, "ends_with");
exportStrFn(str.strConcatC, "concat"); exportStrFn(str.strConcatC, "concat");
exportStrFn(str.strJoinWithC, "joinWith"); exportStrFn(str.strJoinWithC, "joinWith");
@ -85,8 +100,17 @@ comptime {
exportStrFn(str.strFromIntC, "from_int"); exportStrFn(str.strFromIntC, "from_int");
exportStrFn(str.strFromFloatC, "from_float"); exportStrFn(str.strFromFloatC, "from_float");
exportStrFn(str.strEqual, "equal"); exportStrFn(str.strEqual, "equal");
exportStrFn(str.strToBytesC, "to_bytes"); exportStrFn(str.strToUtf8C, "to_utf8");
exportStrFn(str.fromUtf8C, "from_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 // 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 { fn exportDictFn(comptime func: anytype, comptime func_name: []const u8) void {
exportBuiltinFn(func, "dict." ++ func_name); exportBuiltinFn(func, "dict." ++ func_name);
} }
fn exportListFn(comptime func: anytype, comptime func_name: []const u8) void { fn exportListFn(comptime func: anytype, comptime func_name: []const u8) void {
exportBuiltinFn(func, "list." ++ func_name); 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 // Run all tests in imported modules
// https://github.com/ziglang/zig/blob/master/lib/std/std.zig#L94 // https://github.com/ziglang/zig/blob/master/lib/std/std.zig#L94
test "" { test "" {
testing.refAllDecls(@This()); 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;
}

View file

@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const always_inline = std.builtin.CallOptions.Modifier.always_inline; const always_inline = std.builtin.CallOptions.Modifier.always_inline;
const math = std.math; const math = std.math;
const RocList = @import("list.zig").RocList;
pub fn atan(num: f64) callconv(.C) f64 { pub fn atan(num: f64) callconv(.C) f64 {
return @call(.{ .modifier = always_inline }, math.atan, .{num}); 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 { pub fn asin(num: f64) callconv(.C) f64 {
return @call(.{ .modifier = always_inline }, math.asin, .{num}); 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)));
}

View file

@ -865,8 +865,8 @@ pub fn startsWith(string: RocStr, prefix: RocStr) callconv(.C) bool {
return true; return true;
} }
// Str.startsWithCodePoint // Str.startsWithCodePt
pub fn startsWithCodePoint(string: RocStr, prefix: u32) callconv(.C) bool { pub fn startsWithCodePt(string: RocStr, prefix: u32) callconv(.C) bool {
const bytes_len = string.len(); const bytes_len = string.len();
const bytes_ptr = string.asU8ptr(); const bytes_ptr = string.asU8ptr();
@ -886,18 +886,18 @@ pub fn startsWithCodePoint(string: RocStr, prefix: u32) callconv(.C) bool {
return true; return true;
} }
test "startsWithCodePoint: ascii char" { test "startsWithCodePt: ascii char" {
const whole = RocStr.init("foobar", 6); const whole = RocStr.init("foobar", 6);
const prefix = 'f'; 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 yes = RocStr.init("💖foobar", 10);
const no = RocStr.init("foobar", 6); const no = RocStr.init("foobar", 6);
const prefix = '💖'; const prefix = '💖';
try expect(startsWithCodePoint(yes, prefix)); try expect(startsWithCodePt(yes, prefix));
try expect(!startsWithCodePoint(no, prefix)); try expect(!startsWithCodePt(no, prefix));
} }
test "startsWith: foo starts with fo" { test "startsWith: foo starts with fo" {
@ -1129,8 +1129,8 @@ test "RocStr.joinWith: result is big" {
try expect(roc_result.eq(result)); try expect(roc_result.eq(result));
} }
// Str.toBytes // Str.toUtf8
pub fn strToBytesC(arg: RocStr) callconv(.C) RocList { pub fn strToUtf8C(arg: RocStr) callconv(.C) RocList {
return @call(.{ .modifier = always_inline }, strToBytes, .{arg}); return @call(.{ .modifier = always_inline }, strToBytes, .{arg});
} }
@ -1156,6 +1156,11 @@ const FromUtf8Result = extern struct {
problem_code: Utf8ByteProblem, problem_code: Utf8ByteProblem,
}; };
const CountAndStart = extern struct {
count: usize,
start: usize,
};
pub fn fromUtf8C(arg: RocList, output: *FromUtf8Result) callconv(.C) void { pub fn fromUtf8C(arg: RocList, output: *FromUtf8Result) callconv(.C) void {
output.* = @call(.{ .modifier = always_inline }, fromUtf8, .{arg}); 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 } { fn errorToProblem(bytes: [*]u8, length: usize) struct { index: usize, problem: Utf8ByteProblem } {
var index: usize = 0; var index: usize = 0;

View file

@ -1,6 +1,10 @@
const std = @import("std"); const std = @import("std");
const always_inline = std.builtin.CallOptions.Modifier.always_inline; 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! // 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; 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. // This should never be passed a null pointer.
extern fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void; 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 { comptime {
// During tetsts, use the testing allocators to satisfy these functions. // During tetsts, use the testing allocators to satisfy these functions.
if (std.builtin.is_test) { if (std.builtin.is_test) {
@export(testing_roc_alloc, .{ .name = "roc_alloc", .linkage = .Strong }); @export(testing_roc_alloc, .{ .name = "roc_alloc", .linkage = .Strong });
@export(testing_roc_realloc, .{ .name = "roc_realloc", .linkage = .Strong }); @export(testing_roc_realloc, .{ .name = "roc_realloc", .linkage = .Strong });
@export(testing_roc_dealloc, .{ .name = "roc_dealloc", .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); 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 { pub fn alloc(size: usize, alignment: u32) [*]u8 {
return @ptrCast([*]u8, @call(.{ .modifier = always_inline }, roc_alloc, .{ size, alignment })); 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 }); 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 Inc = fn (?[*]u8) callconv(.C) void;
pub const IncN = fn (?[*]u8, u64) callconv(.C) void; pub const IncN = fn (?[*]u8, u64) callconv(.C) void;
pub const Dec = fn (?[*]u8) callconv(.C) void; pub const Dec = fn (?[*]u8) callconv(.C) void;

View file

@ -55,7 +55,7 @@ and : Bool, Bool -> Bool
## ##
## In some languages, `&&` and `||` are special-cased in the compiler to skip ## In some languages, `&&` and `||` are special-cased in the compiler to skip
## evaluating the expression after the operator under certain circumstances. ## 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 or : Bool, Bool -> Bool
## Exclusive or ## 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. ## 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. ## 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. ## 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 ## Note that `isEq` takes `'val` instead of `val`, which means `isEq` does not
## accept arguments whose types contain functions. ## accept arguments whose types contain functions.

View file

@ -1,5 +1,21 @@
interface Dict interface Dict
exposes [ isEmpty, map ] exposes
[
Dict,
empty,
single,
get,
walk,
insert,
len,
remove,
contains,
keys,
values,
union,
intersection,
difference
]
imports [] imports []
size : Dict * * -> Nat size : Dict * * -> Nat
@ -14,7 +30,7 @@ isEmpty : Dict * * -> Bool
## >>> Dict.map {[ "", "a", "bc" ]} Str.isEmpty ## >>> Dict.map {[ "", "a", "bc" ]} Str.isEmpty
## ##
## `map` functions like this are common in Roc, and they all work similarly. ## `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 : map :
Dict beforeKey beforeValue, Dict beforeKey beforeValue,
({ key: beforeKey, value: beforeValue } -> { key: afterKey, value: afterValue }) ({ key: beforeKey, value: beforeValue } -> { key: afterKey, value: afterValue })

View file

@ -1,79 +1,64 @@
interface List2 interface List
exposes exposes
[ List [
, single List,
, empty isEmpty,
, repeat get,
, range set,
, reverse append,
, sort map,
, map len,
, mapWithIndex walkBackwards,
, mapOrCancel concat,
, mapOks first,
, update single,
, updater repeat,
, allOks reverse,
, append prepend,
, prepend join,
, concat keepIf,
, join contains,
, joinMap sum,
, oks walk,
, zip last,
, zipMap keepOks,
, keepIf keepErrs,
, dropIf mapWithIndex,
, first map2,
, last map3,
, get product,
, max walkUntil,
, min range,
, put sortWith,
, drop drop,
, append swap
, prepend
, dropLast
, dropFirst
, takeFirst
, takeLast
, split
, sublist
, walk
, walkBackwards
, walkUntil
, walkBackwardsUntil
, len
, isEmpty
, contains
, all
, any
] ]
imports [] imports []
## Types ## Types
## A sequential list of values. ## 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 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 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 : List [ IntElem I64, StrElem Str ]*
## mixedList = [ IntElem 1, IntElem 2, StrElem "a", StrElem "b" ] ## 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 ## 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) ## 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 ## is normally enabled, not having enough memory could result in the list appearing
## to be created just fine, but then crashing later.) ## to be created just fine, but then crashing later.)
## ##
## > The theoretical maximum length for a list created in Roc is ## > The theoretical maximum length for a list created in Roc is half of
## > #Int.maxNat divided by 2. Attempting to create a list bigger than that ## > `Num.maxNat`. Attempting to create a list bigger than that
## > in Roc code will always fail, although in practice it is likely to fail ## > in Roc code will always fail, although in practice it is likely to fail
## > at much smaller lengths due to insufficient memory being available. ## > at much smaller lengths due to insufficient memory being available.
## ##
@ -147,13 +132,13 @@ interface List2
## ##
## List.first (getRatings 5).bar ## 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 ## 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 ## decremented all the way to 0. At that point, nothing is referencing the list
## anymore, and its memory will get freed. ## anymore, and its memory will get freed.
## ##
## Things are different if this is a list of lists instead of a list of numbers. ## 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. ## and then with a list of lists, to see how they differ.
## ##
## Here's the example using a list of numbers. ## Here's the example using a list of numbers.
@ -165,7 +150,7 @@ interface List2
## ##
## first ## 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: ## 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 ## their own refcounts - to go inside that list. (The empty list at the end
## does not use heap memory, and thus has no refcount.) ## 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. ## * 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 ## * 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. ## * 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! ## * 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 ] List elem : [ @List elem ]
## Initialize ## Initialize
@ -262,18 +247,18 @@ sortDesc : List elem, (elem -> Num *) -> List elem
## > List.map [ "", "a", "bc" ] Str.isEmpty ## > List.map [ "", "a", "bc" ] Str.isEmpty
## ##
## `map` functions like this are common in Roc, and they all work similarly. ## `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 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. ## of the element to the conversion function.
mapWithIndex : List before, (before, Nat -> after) -> List after 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. ## cancel the entire operation immediately, and return that #Err.
mapOrCancel : List before, (before -> Result after err) -> Result (List after) 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. ## wrapped in `Ok` are kept. Any that are wrapped in `Err` are dropped.
## ##
## >>> List.mapOks [ [ "a", "b" ], [], [], [ "c", "d", "e" ] ] List.last ## >>> List.mapOks [ [ "a", "b" ], [], [], [ "c", "d", "e" ] ] List.last
@ -287,16 +272,16 @@ mapOks : List before, (before -> Result after *) -> List after
## the given function. ## the given function.
## ##
## For a version of this which gives you more control over when to perform ## 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 ## ## Performance notes
## ##
## In particular when updating nested collections, this is potentially much more ## 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. ## and then putting it back in the same place.
update : List elem, Nat, (elem -> elem) -> List elem 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. ## that lets you delay performing the update until later.
updater : List elem, Nat -> { elem, new : (elem -> List elem) } updater : List elem, Nat -> { elem, new : (elem -> List elem) }
@ -337,15 +322,15 @@ concat : List elem, List elem -> List elem
## >>> List.join [] ## >>> List.join []
join : List (List elem) -> List elem 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. ## in a list. At the end, all the lists get joined together into one list.
joinMap : List before, (before -> List after) -> List after 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. ## tagged with `Err` are dropped.
## ##
## This can be useful after using an operation that returns a #Result ## 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 ] ] ## >>> [ [ 1, 2, 3 ], [], [], [ 4, 5 ] ]
## >>> |> List.map List.first ## >>> |> List.map List.first
@ -387,16 +372,16 @@ zipMap : List a, List b, (a, b -> c) -> List c
## ##
## ## Performance Details ## ## 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 ## 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 ## 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. ## 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 ## (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.) ## 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 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 ## If all elements in the list end up being kept, Roc will return the original
## list unaltered. ## list unaltered.
@ -410,7 +395,7 @@ keepIf : List elem, (elem -> Bool) -> List elem
## ##
## ## Performance Details ## ## 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! ## See its documentation for details on those characteristics!
dropIf : List elem, (elem -> Bool) -> List elem 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 ## If the given index is outside the bounds of the list, returns the original
## list unmodified. ## 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 set : List elem, Nat, elem -> List elem
## Drops the element at the given index from the list. ## 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. ## 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 drop : List elem, Nat -> List elem
## Adds a new element to the end of the list. ## Adds a new element to the end of the list.
@ -466,12 +451,12 @@ append : List elem, elem -> List elem
## ## Performance Details ## ## Performance Details
## ##
## This always clones the entire list, even when given a Unique list. That means ## 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 ## 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, ## [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 ## 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 ## [List.append] will run at about the same speed—since [List.append] always
## has to clone and grow. ## has to clone and grow.
## ##
## | Unique list | Shared list | ## | Unique list | Shared list |
@ -493,11 +478,11 @@ prepend : List elem, elem -> List elem
## ##
## ## Performance Details ## ## Performance Details
## ##
## Calling #List.pop on a Unique list runs extremely fast. It's essentially ## 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, ## the same as a [List.last] except it also returns the [List] it was given,
## with its length decreased by 1. ## 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 ## copies over every element in the original list except the last one. This
## takes much longer. ## takes much longer.
dropLast : List elem -> Result { others : List elem, last : elem } [ ListWasEmpty ]* 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 ## ## Performance Details
## ##
## When calling either #List.dropFirst or #List.dropLast on a Unique list, #List.dropLast ## 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 ## 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, ## 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 ## 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. ## 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 | ## | Unique list | Shared list |
##-----------+----------------------------------+---------------------------------+ ##-----------+----------------------------------+---------------------------------+
## dropFirst | #List.last + length change | #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 | ## dropLast | [List.last] + clone rest of list | [List.last] + clone rest of list |
dropFirst : List elem -> Result { first: elem, others : List elem } [ ListWasEmpty ]* dropFirst : List elem -> Result { first: elem, others : List elem } [ ListWasEmpty ]*
## Returns the given number of elements from the beginning of the list. ## 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 ] ## >>> 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, ## 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 ## ## Performance Details
## ##
## When given a Unique list, this runs extremely fast. It sets the list's length ## 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 ## 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 ## 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. ## which introduces a conditional bounds check as well as a memory load.
takeFirst : List elem, Nat -> List elem takeFirst : List elem, Nat -> List elem
@ -561,22 +546,22 @@ takeFirst : List elem, Nat -> List elem
## ##
## >>> List.takeLast 5 [ 1, 2 ] ## >>> 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, ## 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 ## ## Performance Details
## ##
## When given a Unique list, this runs extremely fast. It moves the list's ## 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, ## pointer to the index at the given length value, updates its length,
## and frees the leftover elements. This runs very nearly as fast as ## 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 ## 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. ## which introduces a conditional bounds check as well as a memory load.
takeLast : List elem, Nat -> List elem 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 ] ## >>> 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 ## > 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. ## Some languages have a function called **`slice`** which works similarly to this.
sublist : List elem, { start : Nat, len : Nat } -> List elem 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`) ## * `state` starts at 0 (because of `start: 0`)
## * Each `step` runs `Num.add state elem`, and the return value becomes the new `state`. ## * 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`. ## `[ 2, 4, 8 ]` using #Num.add as its `step` function to determine the next `state`.
## ##
## `state` | `elem` | `step state elem` (`Num.add state elem`) ## `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`. ## `fold`, `foldRight`, or `foldr`.
walkBackwards : List elem, { start : state, step : (state, elem -> state) } -> state 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 ## ## 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. ## improve performance) at the cost of making each step take longer.
## However, the added cost to each step is extremely small, and can easily ## 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. ## 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. ## 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 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 walkBackwardsUntil : List elem, { start : state, step : (state, elem -> [ Continue state, Done state ]) } -> state
## Check ## Check
## Returns the length of the list - the number of elements it contains. ## 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 ## 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. ## returns can always be safely converted to an #I32 without losing any data.
len : List * -> Nat len : List * -> Nat

View file

@ -1,10 +1,97 @@
interface Num2 interface Num
exposes [ Num, neg, abs, add, sub, mul, isOdd, isEven, isPositive, isNegative, isZero ] 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 [] 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: ## 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 ## technically has the type `Num (Integer *)`, so when you pass two of them to
## [Num.add], the answer you get is `2 : Num (Integer *)`. ## [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)`, ## 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 : Frac *`. ## 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 ## 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 ## `Num (Integer a)`, so `2 : Num (Integer *)` is the same value as
## `2 : Int *`. ## `2 : Int *`.
@ -40,7 +127,7 @@ interface Num2
## ends up having the type `Nat`. ## ends up having the type `Nat`.
## ##
## Sometimes number literals don't become more specific. For example, ## 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)` ## when calling `Num.toStr (5 + 6)`, the expression `(5 + 6)`
## still has the type `Num *`. When this happens, `Num *` defaults to ## still has the type `Num *`. When this happens, `Num *` defaults to
## being an [I64] - so this addition expression would overflow ## 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 ## [Dec] typically takes slightly less time than [F64] to perform addition and
## subtraction, but 10-20 times longer to perform multiplication and division. ## subtraction, but 10-20 times longer to perform multiplication and division.
## [sqrt] and trigonometry are massively slower with [Dec] than with [F64]. ## [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. ## 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 ## 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 ## 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. ## 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. ## 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 ## >>> 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. ## 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 ## 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. ## 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 neg : Num a -> Num a
## Return the absolute value of the number. ## Return the absolute value of the number.
@ -356,7 +443,7 @@ neg : Num a -> Num a
## ##
## >>> Num.abs 0.0 ## >>> 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. ## 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 ## 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. ## 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`. ## `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. ## 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`. ## `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. ## 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`. ## `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 ## >>> 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 ## >>> Num.toStr 4.2
## ##
@ -553,16 +640,16 @@ format :
-> Str -> Str
## Round off the given float to the nearest integer. ## Round off the given float to the nearest integer.
round : Frac * -> Int * round : Float * -> Int *
ceil : Frac * -> Int * ceil : Float * -> Int *
floor : Frac * -> Int * floor : Float * -> Int *
trunc : Frac * -> Int * trunc : Float * -> Int *
## Convert an #Int to a #Nat. If the given number doesn't fit in #Nat, it will be truncated. ## 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 ## 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, 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 ## #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 ## 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. ## 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 ## 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. ## 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. ## on it, then call this on the resulting #Int.
toNat : Int * -> Nat toNat : Int * -> Nat
## Convert an #Int to an #I8. If the given number doesn't fit in #I8, it will be truncated. ## 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. ## on it, then call this on the resulting #Int.
toI8 : Int * -> I8 toI8 : Int * -> I8
toI16 : Int * -> I16 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, ## Modulo is the same as remainder when working with positive numbers,
## but if either number is negative, then modulo works differently. ## 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. ## Return `Err DivByZero` if the second integer is zero, because division by zero is undefined in mathematics.
## ##
@ -749,28 +836,28 @@ maxDec : Dec
## Constants ## Constants
## An approximation of e, specifically 2.718281828459045. ## An approximation of e, specifically 2.718281828459045.
e : Frac * e : Float *
## An approximation of pi, specifically 3.141592653589793. ## An approximation of pi, specifically 3.141592653589793.
pi : Frac * pi : Float *
## Trigonometry ## 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?) ## Other Calculations (arithmetic?)
## Divide one [Frac] by another. ## Divide one [Float] by another.
## ##
## `a / b` is shorthand for `Num.div a b`. ## `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 ## > cost! Since the most common reason to choose [F64] or [F32] over [Dec] is
## > access to hardware-accelerated performance, Roc follows these rules exactly. ## > 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]. ## one of the functions in this module like [toDec].
## ##
## >>> 5.0 / 7.0 ## >>> 5.0 / 7.0
@ -800,9 +887,9 @@ atan : Frac a -> Frac a
## ##
## >>> Num.pi ## >>> Num.pi
## >>> |> Num.div 2.0 ## >>> |> 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, ## Modulo is the same as remainder when working with positive numbers,
## but if either number is negative, then modulo works differently. ## but if either number is negative, then modulo works differently.
@ -825,20 +912,20 @@ div : Frac a, Frac a -> Frac a
## ##
## >>> Num.pi ## >>> Num.pi
## >>> |> Num.mod 2.0 ## >>> |> 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. ## 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 ## Raises an integer to the power of another, by multiplying the integer by
## itself the given number of times. ## itself the given number of times.
## ##
## This process is known as [exponentiation by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring). ## 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. ## see #Num.exp.
## ##
## >>> Num.exp 5 0 ## >>> Num.exp 5 0
@ -855,9 +942,9 @@ pow : Frac a, Frac a -> Frac a
## overflow ## overflow
expBySquaring : Int a, U8 -> Int a 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 ## 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. ## 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.0f64
## ##
## >>> Frac.sqrt -4.0dec ## >>> Frac.sqrt -4.0dec
sqrt : Frac a -> Frac a sqrt : Float a -> Float a
## Bit shifts ## Bit shifts
@ -909,23 +996,22 @@ shr : Int a, Int a -> Int a
## the beginning. (In contrast, [shr] replaces discarded bits with zeroes.) ## the beginning. (In contrast, [shr] replaces discarded bits with zeroes.)
shrWrap : Int a, Int a -> Int a shrWrap : Int a, Int a -> Int a
## [Endianness](https://en.wikipedia.org/wiki/Endianness) ## [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. ## only one byte.
toBytes : Num *, Endi -> List U8 # toBytes : Num *, Endi -> List U8
## when Num.parseBytes bytes Big is ## when Num.parseBytes bytes Big is
## Ok { val: f64, rest } -> ... ## Ok { val: f64, rest } -> ...
## Err (ExpectedNum (Float Binary64)) -> ... ## 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 ## when Num.fromBytes bytes Big is
## Ok f64 -> ... ## Ok f64 -> ...
## Err (ExpectedNum (Float Binary64)) -> ... ## Err (ExpectedNum (Float Binary64)) -> ...
fromBytes : List U8, Endi -> Result (Num a) [ ExpectedNum a ]* # fromBytes : List U8, Endi -> Result (Num a) [ ExpectedNum a ]*
## Comparison ## 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 ## 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. ## 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 ## Although this can be passed to `List.sort`, you'll get better performance
## by using [List.sortAsc] or [List.sortDesc] instead. ## by using `List.sortAsc` or `List.sortDesc` instead.
compare : Num a, Num a -> [ Lt, Eq, Gt ] compare : Num a, Num a -> [ Lt, Eq, Gt ]
## Special Floating-Point Values ## 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 ## This is the opposite of [isInfinite], except when given [*NaN*](Num.isNaN). Both
## [isFinite] and [isInfinite] return `False` for [*NaN*](Num.isNaN). ## [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 ## When given a [F64] or [F32] value, returns `True` if that value is either
## ∞ or -∞, and `False` otherwise. ## ∞ or -∞, and `False` otherwise.
@ -1014,7 +1100,7 @@ isFinite : Frac * -> Bool
## ##
## This is the opposite of [isFinite], except when given [*NaN*](Num.isNaN). Both ## This is the opposite of [isFinite], except when given [*NaN*](Num.isNaN). Both
## [isFinite] and [isInfinite] return `False` for [*NaN*](Num.isNaN). ## [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 ## 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. ## *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 ## 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 ## 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. ## collections! See the documentation for [Set.add] and [Dict.insert] for details.
isNaN : Frac * -> Bool isNaN : Float * -> Bool

View file

@ -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

View file

@ -1,9 +1,22 @@
interface Set 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 [] imports []
## Set
## A Set is an unordered collection of unique elements. ## A Set is an unordered collection of unique elements.
Set elem : [ @Set elem ] Set elem : [ @Set elem ]
@ -36,7 +49,7 @@ drop : Set elem, elem -> Set elem
## >>> Set.map {: "", "a", "bc" :} Str.isEmpty ## >>> Set.map {: "", "a", "bc" :} Str.isEmpty
## ##
## `map` functions like this are common in Roc, and they all work similarly. ## `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 # TODO: removed `'` from signature because parser does not support it yet
# Original signature: `map : Set 'elem, ('before -> 'after) -> Set 'after` # Original signature: `map : Set 'elem, ('before -> 'after) -> Set 'after`
map : Set elem, (before -> after) -> Set after map : Set elem, (before -> after) -> Set after

View file

@ -2,36 +2,21 @@ interface Str
exposes exposes
[ [
Str, Str,
decimal,
split,
isEmpty, isEmpty,
append,
concat,
joinWith,
split,
countGraphemes,
startsWith, startsWith,
endsWith, endsWith,
contains, fromInt,
anyGraphemes, fromFloat,
allGraphemes, fromUtf8,
join, Utf8Problem,
joinWith, Utf8ByteProblem,
padGraphemesStart,
padGraphemesEnd,
graphemes,
reverseGraphemes,
isCaseInsensitiveEq,
isCaseInsensitiveNeq,
walkGraphemes,
isCapitalized,
isAllUppercase,
isAllLowercase,
toUtf8, toUtf8,
toUtf16, startsWithCodePt
toUtf32,
trim,
walkUtf8,
walkUtf16,
walkUtf32,
walkRevUtf8,
walkRevUtf16,
walkRevUtf32
] ]
imports [] imports []
@ -63,7 +48,7 @@ interface Str
## programming, and "extended grapheme cluster" is a mouthful, in Roc we use the ## 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." ## 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 "Roc!"
## Str.countGraphemes "折り紙" ## Str.countGraphemes "折り紙"
@ -126,7 +111,7 @@ interface Str
## potentially change it without breaking existing Roc applications. (UTF-8 ## 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.) ## 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) ## 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, ## mentioned earlier) in a particular encoding. If you need encoding-specific functions,
## you should take a look at the [roc/unicode](roc/unicode) package. ## you should take a look at the [roc/unicode](roc/unicode) package.
@ -137,15 +122,15 @@ Str : [ @Str ]
## Convert ## 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 ## If you want to keep all the digits, use [Str.num] instead.
## 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.
decimal : Float *, Nat -> Str decimal : Float *, Nat -> Str
## Convert a [Num] to a string.
num : Float *, Nat -> Str
## Split a string around a separator. ## Split a string around a separator.
## ##
## >>> Str.split "1,2,3" "," ## >>> Str.split "1,2,3" ","
@ -155,13 +140,13 @@ decimal : Float *, Nat -> Str
## ##
## >>> Str.split "1,2,3" "" ## >>> 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 : Str, Str -> List Str
## Split a string around newlines. ## Split a string around newlines.
## ##
## On strings that use `"\n"` for their line endings, this gives the same answer ## 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 ## as [in Windows files](https://en.wikipedia.org/wiki/Newline#History)), this
## will consume the entire `"\n\r"` instead of just the `"\n"`. ## 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!" ## >>> 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. ## string splitting, use a #Parser.
lines : Str, Str -> List Str lines : Str, Str -> List Str
## Check ## Check
## Returns #True if the string is empty, and #False otherwise. ## Returns `True` if the string is empty, and `False` otherwise.
## ##
## >>> Str.isEmpty "hi!" ## >>> Str.isEmpty "hi!"
## ##
@ -192,13 +177,13 @@ startsWith : Str, Str -> Bool
## ##
## **Performance Note:** This runs slightly faster than [Str.startsWith], so ## **Performance Note:** This runs slightly faster than [Str.startsWith], so
## if you want to check whether a string begins with something that's representable ## 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 '鹏'` ## in a single code point, you can use (for example) `Str.startsWithCodePt '鹏'`
## instead of `Str.startsWithCodePoint "鹏"`. ('鹏' evaluates to the [U32] ## instead of `Str.startsWithCodePt "鹏"`. ('鹏' evaluates to the [U32]
## value `40527`.) This will not work for graphemes which take up multiple code ## 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 ## because 👩‍👩‍👦‍👦 takes up multiple code points and cannot be represented as a
## single [U32]. You'd need to use `Str.startsWithCodePoint "🕊"` instead. ## single [U32]. You'd need to use `Str.startsWithCodePt "🕊"` instead.
startsWithCodePoint : Str, U32 -> Bool startsWithCodePt : Str, U32 -> Bool
endsWith : Str, Str -> Bool endsWith : Str, Str -> Bool
@ -255,9 +240,13 @@ padGraphemesEnd : Str, Nat, Str -> Str
## ##
graphemes : Str -> List 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 "Roc!" # 4
## Str.countGraphemes "七巧板" # 3 ## Str.countGraphemes "七巧板" # 3
## Str.countGraphemes "🕊" # 1 ## Str.countGraphemes "🕊" # 1
countGraphemes : Str -> Nat
## Reverse the order of the string's individual graphemes. ## Reverse the order of the string's individual graphemes.
## ##
@ -268,7 +257,7 @@ graphemes : Str -> List Str
## >>> Str.reversegraphemes "Crème Brûlée" ## >>> Str.reversegraphemes "Crème Brûlée"
reverseGraphemes : Str -> Str 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" ## >>> Str.caseInsensitiveEq "hi" "Hi"
isCaseInsensitiveEq : Str, Str -> Bool 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 walkGraphemesBackwards : Str, { start: state, step: (state, Str -> state) } -> state
walkGraphemesBackwardsUntil : Str, { start: state, step: (state, Str -> [ Continue state, Done 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" ## >>> Str.isCapitalized "Hi"
## ##
@ -305,7 +294,7 @@ walkGraphemesBackwardsUntil : Str, { start: state, step: (state, Str -> [ Contin
## package for functions which capitalize strings. ## package for functions which capitalize strings.
isCapitalized : Str -> Bool 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" ## >>> Str.isAllUppercase "hi"
## ##
@ -326,7 +315,7 @@ isCapitalized : Str -> Bool
## >>> Str.isAllUppercase "" ## >>> Str.isAllUppercase ""
isAllUppercase : Str -> Bool 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" ## >>> 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), ## 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. ## return a [Str] containing only that scalar.
fromScalar : U32 -> Result Str [ BadScalar ]* 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 ]* 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). ## 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). # ## 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 ]* # 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). # ## 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 ]* # 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) # ## 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 ]* # 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) # ## 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 ]* # 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). # ## 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 ]* # fromUtf32Bom : List U8 -> Result Str [ BadUtf32 Endi, NoBom ]*
## Convert from UTF-8, substituting the replacement character ("<22>") for any # ## Convert from UTF-8, substituting the replacement character ("<22>") for any
## invalid sequences encountered. # ## invalid sequences encountered.
fromUtf8Sub : List U8 -> Str # fromUtf8Sub : List U8 -> Str
fromUtf16Sub : List U8, Endi -> Str # fromUtf16Sub : List U8, Endi -> Str
fromUtf16BomSub : List U8 -> Result Str [ NoBom ]* # fromUtf16BomSub : List U8 -> Result Str [ NoBom ]*
## Return a #List of the string's #U8 UTF-8 [code units](https://unicode.org/glossary/#code_unit). ## 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, ## (To split the string into a [List] of smaller [Str] values instead of [U8] values,
## see #Str.split and #Str.graphemes.) ## see [Str.split] and `Str.graphemes`.)
## ##
## >>> Str.toUtf8 "👩‍👩‍👦‍👦" ## >>> Str.toUtf8 "👩‍👩‍👦‍👦"
## ##
@ -393,15 +382,15 @@ fromUtf16BomSub : List U8 -> Result Str [ NoBom ]*
## ##
## >>> Str.toUtf8 "🐦" ## >>> Str.toUtf8 "🐦"
## ##
## For a more flexible function that walks through each of these #U8 code units ## For a more flexible function that walks through each of these [U8] code units
## without creating a #List, see #Str.walkUtf8 and #Str.walkRevUtf8. ## without creating a [List], see `Str.walkUtf8` and `Str.walkRevUtf8`.
toUtf8 : Str -> List U8 toUtf8 : Str -> List U8
toUtf16Be : Str -> List U8 toUtf16Be : Str -> List U8
toUtf16Le : Str -> List U8 toUtf16Le : Str -> List U8
toUtf16Bom : Str, Endi -> List U8 # toUtf16Bom : Str, Endi -> List U8
toUtf32Be : Str -> List U8 toUtf32Be : Str -> List U8
toUtf32Le : Str -> List U8 toUtf32Le : Str -> List U8
toUtf32Bom : Str, Endi -> List U8 # toUtf32Bom : Str, Endi -> List U8
# Parsing # 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 ## If the string does not begin with a valid code point, for example because it was
## empty, return `Err`. ## 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 ## If the first string begins with the second, return whatever comes
## after the second. ## 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) ## 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. ## 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]. ## For more advanced options, see [parseU8].
toU8 : Str -> Result U8 [ InvalidU8 ]* 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 ## If the string begins with `"NaN"`, `"∞"`, and `"-∞"` (which do not represent
## [finite](Num.isFinite) numbers), they will be accepted only when parsing ## [finite](Num.isFinite) numbers), they will be accepted only when parsing
## [F64] or [F32] numbers, and translated accordingly. ## [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: ## Notes:
## * You can allow a decimal mark for integers; they'll only parse if the numbers after it are all 0. ## * 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`, `Required` has a payload for how many digits (e.g. "required every 3 digits")
## * For `wholeSep`, `Allowed` allows the separator to appear anywhere. ## * For `wholeSep`, `Allowed` allows the separator to appear anywhere.
NumParseConfig : # NumParseConfig :
{ # {
base ? [ Decimal, Hexadecimal, Octal, Binary ], # base ? [ Decimal, Hexadecimal, Octal, Binary ],
notation ? [ Standard, Scientific, Any ], # notation ? [ Standard, Scientific, Any ],
decimalMark ? [ Allowed Str, Required Str, Disallowed ], # decimalMark ? [ Allowed Str, Required Str, Disallowed ],
decimalDigits ? [ Any, AtLeast U16, Exactly U16 ], # decimalDigits ? [ Any, AtLeast U16, Exactly U16 ],
wholeDigits ? [ Any, AtLeast U16, Exactly U16 ], # wholeDigits ? [ Any, AtLeast U16, Exactly U16 ],
leadingZeroes ? [ Allowed, Disallowed ], # leadingZeroes ? [ Allowed, Disallowed ],
trailingZeroes ? [ Allowed, Disallowed ], # trailingZeroes ? [ Allowed, Disallowed ],
wholeSep ? { mark : Str, policy : [ Allowed, Required U64 ] } # wholeSep ? { mark : Str, policy : [ Allowed, Required U64 ] }
} # }

View file

@ -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_ATAN: &str = "roc_builtins.num.atan";
pub const NUM_IS_FINITE: &str = "roc_builtins.num.is_finite"; 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_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_INIT: &str = "roc_builtins.str.init";
pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments"; 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_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_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: &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_ENDS_WITH: &str = "roc_builtins.str.ends_with";
pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes"; pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes";
pub const STR_FROM_INT: &str = "roc_builtins.str.from_int"; pub const STR_FROM_INT: &str = "roc_builtins.str.from_int";
pub const STR_FROM_FLOAT: &str = "roc_builtins.str.from_float"; pub const STR_FROM_FLOAT: &str = "roc_builtins.str.from_float";
pub const STR_EQUAL: &str = "roc_builtins.str.equal"; 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: &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 = "roc_builtins.dict.hash";
pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str"; 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_CONTAINS: &str = "roc_builtins.list.contains";
pub const LIST_REPEAT: &str = "roc_builtins.list.repeat"; pub const LIST_REPEAT: &str = "roc_builtins.list.repeat";
pub const LIST_APPEND: &str = "roc_builtins.list.append"; 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_DROP: &str = "roc_builtins.list.drop";
pub const LIST_SWAP: &str = "roc_builtins.list.swap"; pub const LIST_SWAP: &str = "roc_builtins.list.swap";
pub const LIST_SINGLE: &str = "roc_builtins.list.single"; 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_CONCAT: &str = "roc_builtins.list.concat";
pub const LIST_SET: &str = "roc_builtins.list.set"; pub const LIST_SET: &str = "roc_builtins.list.set";
pub const LIST_SET_IN_PLACE: &str = "roc_builtins.list.set_in_place"; 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";

View file

@ -4,11 +4,12 @@ use roc_module::symbol::Symbol;
use roc_region::all::Region; use roc_region::all::Region;
use roc_types::builtin_aliases::{ use roc_types::builtin_aliases::{
bool_type, dict_type, float_type, i128_type, int_type, list_type, nat_type, num_type, 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, ordering_type, result_type, set_type, str_type, str_utf8_byte_problem_type, u16_type, u32_type,
u8_type, u64_type, u8_type,
}; };
use roc_types::solved_types::SolvedType; use roc_types::solved_types::SolvedType;
use roc_types::subs::VarId; use roc_types::subs::VarId;
use roc_types::types::RecordField;
use std::collections::HashMap; use std::collections::HashMap;
/// Example: /// Example:
@ -500,6 +501,32 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(float_type(flex(TVAR1))), 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 // Bool module
// and : Bool, Bool -> Bool // and : Bool, Bool -> Bool
@ -563,9 +590,9 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(bool_type()) Box::new(bool_type())
); );
// startsWithCodePoint : Str, U32 -> Bool // startsWithCodePt : Str, U32 -> Bool
add_top_level_function_type!( add_top_level_function_type!(
Symbol::STR_STARTS_WITH_CODE_POINT, Symbol::STR_STARTS_WITH_CODE_PT,
vec![str_type(), u32_type()], vec![str_type(), u32_type()],
Box::new(bool_type()) Box::new(bool_type())
); );
@ -592,10 +619,10 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
); );
// fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]* // fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]*
{
let bad_utf8 = SolvedType::TagUnion( let bad_utf8 = SolvedType::TagUnion(
vec![( vec![(
TagName::Global("BadUtf8".into()), TagName::Global("BadUtf8".into()),
// vec![str_utf8_problem_type()],
vec![str_utf8_byte_problem_type(), nat_type()], vec![str_utf8_byte_problem_type(), nat_type()],
)], )],
Box::new(SolvedType::Wildcard), Box::new(SolvedType::Wildcard),
@ -606,10 +633,40 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
vec![list_type(u8_type())], vec![list_type(u8_type())],
Box::new(result_type(str_type(), bad_utf8)), Box::new(result_type(str_type(), bad_utf8)),
); );
}
// 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),
);
// toBytes : Str -> List U8
add_top_level_function_type!( add_top_level_function_type!(
Symbol::STR_TO_BYTES, 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_UTF8,
vec![str_type()], vec![str_type()],
Box::new(list_type(u8_type())) Box::new(list_type(u8_type()))
); );

View file

@ -17,7 +17,6 @@ ven_graph = { path = "../../vendor/pathfinding" }
im = "14" # im and im-rc should always have the same version! 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! im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] } bumpalo = { version = "3.6.1", features = ["collections"] }
inlinable_string = "0.1"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1" pretty_assertions = "0.5.1"

View file

@ -6,7 +6,7 @@ use roc_module::symbol::Symbol;
use roc_parse::ast::{AssignedField, Tag, TypeAnnotation}; use roc_parse::ast::{AssignedField, Tag, TypeAnnotation};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable}; 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)] #[derive(Clone, Debug, PartialEq)]
pub struct Annotation { pub struct Annotation {
@ -159,7 +159,7 @@ fn can_annotation_help(
Err(problem) => { Err(problem) => {
env.problem(roc_problem::can::Problem::RuntimeError(problem)); env.problem(roc_problem::can::Problem::RuntimeError(problem));
return Type::Erroneous(Problem::UnrecognizedIdent(ident.into())); return Type::Erroneous(Problem::UnrecognizedIdent(ident));
} }
} }
} else { } else {
@ -227,14 +227,27 @@ fn can_annotation_help(
} }
// make sure hidden variables are freshly instantiated // make sure hidden variables are freshly instantiated
for var in alias.lambda_set_variables.iter() { let mut lambda_set_variables =
substitutions.insert(var.into_inner(), Type::Variable(var_store.fresh())); 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 // instantiate variables
actual.substitute(&substitutions); actual.substitute(&substitutions);
Type::Alias(symbol, vars, Box::new(actual)) Type::Alias {
symbol,
type_arguments: vars,
lambda_set_variables,
actual: Box::new(actual),
}
} }
None => { None => {
let mut args = Vec::new(); let mut args = Vec::new();
@ -373,12 +386,18 @@ fn can_annotation_help(
introduced_variables.insert_host_exposed_alias(symbol, actual_var); introduced_variables.insert_host_exposed_alias(symbol, actual_var);
Type::HostExposedAlias { Type::HostExposedAlias {
name: symbol, name: symbol,
arguments: vars, type_arguments: vars,
lambda_set_variables: alias.lambda_set_variables.clone(),
actual: Box::new(alias.typ.clone()), actual: Box::new(alias.typ.clone()),
actual_var, actual_var,
} }
} else { } 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()),
}
} }
} }
_ => { _ => {

View file

@ -58,12 +58,13 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
STR_SPLIT => str_split, STR_SPLIT => str_split,
STR_IS_EMPTY => str_is_empty, STR_IS_EMPTY => str_is_empty,
STR_STARTS_WITH => str_starts_with, 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_ENDS_WITH => str_ends_with,
STR_COUNT_GRAPHEMES => str_count_graphemes, STR_COUNT_GRAPHEMES => str_count_graphemes,
STR_FROM_INT => str_from_int, STR_FROM_INT => str_from_int,
STR_FROM_UTF8 => str_from_utf8, 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, STR_FROM_FLOAT=> str_from_float,
LIST_LEN => list_len, LIST_LEN => list_len,
LIST_GET => list_get, LIST_GET => list_get,
@ -160,6 +161,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_ATAN => num_atan, NUM_ATAN => num_atan,
NUM_ACOS => num_acos, NUM_ACOS => num_acos,
NUM_ASIN => num_asin, 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_MAX_INT => num_max_int,
NUM_MIN_INT => num_min_int, NUM_MIN_INT => num_min_int,
NUM_BITWISE_AND => num_bitwise_and, NUM_BITWISE_AND => num_bitwise_and,
@ -455,8 +458,7 @@ fn num_add_wrap(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumAddWrap) num_binop(symbol, var_store, LowLevel::NumAddWrap)
} }
/// Num.addChecked : Num a, Num a -> Result (Num a) [ Overflow ]* fn num_overflow_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel) -> Def {
fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh(); let bool_var = var_store.fresh();
let num_var_1 = var_store.fresh(); let num_var_1 = var_store.fresh();
let num_var_2 = 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 ret_var = var_store.fresh();
let record_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 // if arg_3.b then
// # overflow // # 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 { let def = crate::def::Def {
loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_3)), loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_3)),
loc_expr: no_region(RunLowLevel { loc_expr: no_region(RunLowLevel {
op: LowLevel::NumAddChecked, op: lowlevel,
args: vec![ args: vec![
(num_var_1, Var(Symbol::ARG_1)), (num_var_1, Var(Symbol::ARG_1)),
(num_var_2, Var(Symbol::ARG_2)), (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 /// Num.sub : Num a, Num a -> Num a
fn num_sub(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_sub(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumSub) 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 ]* /// Num.subChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
fn num_sub_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_sub_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh(); num_overflow_checked(symbol, var_store, LowLevel::NumSubChecked)
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.mul : Num a, Num a -> Num a /// 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 ]* /// Num.mulChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
fn num_mul_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_mul_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh(); num_overflow_checked(symbol, var_store, LowLevel::NumMulChecked)
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.isGt : Num a, Num a -> Bool /// 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 /// Num.bitwiseAnd : Int a, Int a -> Int a
fn num_bitwise_and(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_bitwise_and(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumBitwiseAnd) num_binop(symbol, var_store, LowLevel::NumBitwiseAnd)
@ -1451,9 +1300,9 @@ fn str_starts_with(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::StrStartsWith, var_store) 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 { 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 /// 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 { fn str_from_utf8(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bytes_var = var_store.fresh(); let bytes_var = var_store.fresh();
let bool_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, 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 // let arg_3 = RunLowLevel FromUtf8Range arg_1 arg_2
fn str_to_bytes(symbol: Symbol, var_store: &mut VarStore) -> Def { //
lowlevel_1(symbol, LowLevel::StrToBytes, var_store) // 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 /// 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)] #[inline(always)]
fn defn_help( fn defn_help(
fn_name: Symbol, fn_name: Symbol,

View file

@ -60,33 +60,49 @@ impl Constraint {
true 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) { fn subtract(declared: &Declared, detail: &VariableDetail, accum: &mut VariableDetail) {
for var in &detail.type_variables { 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); accum.type_variables.insert(*var);
} }
} }
// lambda set variables are always flex // lambda set variables are always flex
for var in &detail.lambda_set_variables { 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); panic!("lambda set variable {:?} is declared as rigid", var);
} }
if !declared.flex_vars.contains(&var.into_inner()) { if !declared.flex_vars.contains(var) {
accum.lambda_set_variables.insert(*var); accum.lambda_set_variables.push(*var);
} }
} }
// recursion vars should be always rigid // recursion vars should be always rigid
for var in &detail.recursion_variables { 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); 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); accum.recursion_variables.insert(*var);
} }
} }

View file

@ -380,7 +380,7 @@ pub fn sort_can_defs(
// //
// In the above example, `f` cannot reference `a`, and in the closure // In the above example, `f` cannot reference `a`, and in the closure
// a call to `f` cannot cycle back to `a`. // 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 the current symbol is a closure, peek into its body
if let Some(References { lookups, .. }) = env.closures.get(symbol) { 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 // In the above example, `f` cannot reference `a`, and in the closure
// a call to `f` cannot cycle back to `a`. // 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 the current symbol is a closure, peek into its body
if let Some(References { lookups, .. }) = env.closures.get(symbol) { if let Some(References { lookups, .. }) = env.closures.get(symbol) {
@ -454,7 +454,7 @@ pub fn sort_can_defs(
let direct_successors = |symbol: &Symbol| -> ImSet<Symbol> { let direct_successors = |symbol: &Symbol| -> ImSet<Symbol> {
match refs_by_symbol.get(symbol) { match refs_by_symbol.get(symbol) {
Some((_, references)) => { 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 // NOTE: if the symbol is a closure we DONT look into its body
@ -540,7 +540,7 @@ pub fn sort_can_defs(
), ),
Some((region, _)) => { Some((region, _)) => {
let expr_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 { let entry = CycleEntry {
symbol: *symbol, symbol: *symbol,
@ -662,11 +662,11 @@ fn group_to_declaration(
// for a definition, so every definition is only inserted (thus typechecked and emitted) once // for a definition, so every definition is only inserted (thus typechecked and emitted) once
let mut seen_pattern_regions: ImSet<Region> = ImSet::default(); let mut seen_pattern_regions: ImSet<Region> = ImSet::default();
for cycle in strongly_connected_components(&group, filtered_successors) { for cycle in strongly_connected_components(group, filtered_successors) {
if cycle.len() == 1 { if cycle.len() == 1 {
let symbol = &cycle[0]; 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(); let mut new_def = can_def.clone();
// Determine recursivity of closures that are not tail-recursive // Determine recursivity of closures that are not tail-recursive
@ -678,7 +678,7 @@ fn group_to_declaration(
*recursive = closure_recursivity(*symbol, closures); *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 !seen_pattern_regions.contains(&new_def.loc_pattern.region) {
if is_recursive { if is_recursive {
@ -854,7 +854,7 @@ fn canonicalize_pending_def<'a>(
}; };
for (_, (symbol, _)) in scope.idents() { for (_, (symbol, _)) in scope.idents() {
if !vars_by_symbol.contains_key(&symbol) { if !vars_by_symbol.contains_key(symbol) {
continue; continue;
} }
@ -999,7 +999,7 @@ fn canonicalize_pending_def<'a>(
// //
// Only defs of the form (foo = ...) can be closure declarations or self tail calls. // Only defs of the form (foo = ...) can be closure declarations or self tail calls.
if let ( if let (
&ast::Pattern::Identifier(ref _name), &ast::Pattern::Identifier(_name),
&Pattern::Identifier(ref defined_symbol), &Pattern::Identifier(ref defined_symbol),
&Closure { &Closure {
function_type, 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, // 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.) // 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!( panic!(
"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", "Tried to remove symbol {:?} from procedures, but it was not found: {:?}",
symbol, env.closures 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 // Store the referenced locals in the refs_by_symbol map, so we can later figure out
// which defined names reference each other. // which defined names reference each other.
for (_, (symbol, region)) in scope.idents() { for (_, (symbol, region)) in scope.idents() {
if !vars_by_symbol.contains_key(&symbol) { if !vars_by_symbol.contains_key(symbol) {
continue; continue;
} }
@ -1110,10 +1110,8 @@ fn canonicalize_pending_def<'a>(
// identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called.
let outer_identifier = env.tailcallable_symbol; let outer_identifier = env.tailcallable_symbol;
if let ( if let (&ast::Pattern::Identifier(_name), &Pattern::Identifier(ref defined_symbol)) =
&ast::Pattern::Identifier(ref _name), (&loc_pattern.value, &loc_can_pattern.value)
&Pattern::Identifier(ref defined_symbol),
) = (&loc_pattern.value, &loc_can_pattern.value)
{ {
env.tailcallable_symbol = Some(*defined_symbol); 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. // Only defs of the form (foo = ...) can be closure declarations or self tail calls.
if let ( if let (
&ast::Pattern::Identifier(ref _name), &ast::Pattern::Identifier(_name),
&Pattern::Identifier(ref defined_symbol), &Pattern::Identifier(ref defined_symbol),
&Closure { &Closure {
function_type, 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, // 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.) // 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!( panic!(
"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", "Tried to remove symbol {:?} from procedures, but it was not found: {:?}",
symbol, env.closures symbol, env.closures
@ -1555,7 +1553,7 @@ fn correct_mutual_recursive_type_alias<'a>(
let mut loc_succ = alias.typ.symbols(); let mut loc_succ = alias.typ.symbols();
// remove anything that is not defined in the current block // remove anything that is not defined in the current block
loc_succ.retain(|key| symbols_introduced.contains(key)); loc_succ.retain(|key| symbols_introduced.contains(key));
loc_succ.remove(&symbol); loc_succ.remove(symbol);
loc_succ loc_succ
} }
@ -1634,7 +1632,7 @@ fn make_tag_union_recursive<'a>(
typ.substitute_alias(symbol, &Type::Variable(rec_var)); typ.substitute_alias(symbol, &Type::Variable(rec_var));
} }
Type::RecursiveTagUnion(_, _, _) => {} Type::RecursiveTagUnion(_, _, _) => {}
Type::Alias(_, _, actual) => make_tag_union_recursive( Type::Alias { actual, .. } => make_tag_union_recursive(
env, env,
symbol, symbol,
region, region,

View file

@ -1,7 +1,6 @@
use crate::procedure::References; use crate::procedure::References;
use inlinable_string::InlinableString;
use roc_collections::all::{MutMap, MutSet}; 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_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_problem::can::{Problem, RuntimeError}; use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
@ -12,7 +11,7 @@ pub struct Env<'a> {
/// are assumed to be relative to this path. /// are assumed to be relative to this path.
pub home: ModuleId, pub home: ModuleId,
pub dep_idents: MutMap<ModuleId, IdentIds>, pub dep_idents: &'a MutMap<ModuleId, IdentIds>,
pub module_ids: &'a ModuleIds, pub module_ids: &'a ModuleIds,
@ -40,7 +39,7 @@ pub struct Env<'a> {
impl<'a> Env<'a> { impl<'a> Env<'a> {
pub fn new( pub fn new(
home: ModuleId, home: ModuleId,
dep_idents: MutMap<ModuleId, IdentIds>, dep_idents: &'a MutMap<ModuleId, IdentIds>,
module_ids: &'a ModuleIds, module_ids: &'a ModuleIds,
exposed_ident_ids: IdentIds, exposed_ident_ids: IdentIds,
) -> Env<'a> { ) -> 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 /// Returns Err if the symbol resolved, but it was not exposed by the given module
pub fn qualified_lookup( pub fn qualified_lookup(
&mut self, &mut self,
module_name: &str, module_name_str: &str,
ident: &str, ident: &str,
region: Region, region: Region,
) -> Result<Symbol, RuntimeError> { ) -> Result<Symbol, RuntimeError> {
debug_assert!( debug_assert!(
!module_name.is_empty(), !module_name_str.is_empty(),
"Called env.qualified_lookup with an unqualified ident: {:?}", "Called env.qualified_lookup with an unqualified ident: {:?}",
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) { match self.module_ids.get_id(&module_name) {
Some(&module_id) => { Some(&module_id) => {
let ident: InlinableString = ident.into();
// You can do qualified lookups on your own module, e.g. // 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 I'm in the Foo module, I can do a `Foo.bar` lookup.
if module_id == self.home { if module_id == self.home {
@ -114,7 +112,7 @@ impl<'a> Env<'a> {
Ok(symbol) Ok(symbol)
} }
None => Err(RuntimeError::ValueNotExposed { None => Err(RuntimeError::ValueNotExposed {
module_name: ModuleName::from(module_name), module_name,
ident, ident,
region, region,
}), }),

View file

@ -9,7 +9,6 @@ use crate::num::{
use crate::pattern::{canonicalize_pattern, Pattern}; use crate::pattern::{canonicalize_pattern, Pattern};
use crate::procedure::References; use crate::procedure::References;
use crate::scope::Scope; use crate::scope::Scope;
use inlinable_string::InlinableString;
use roc_collections::all::{ImSet, MutMap, MutSet, SendMap}; use roc_collections::all::{ImSet, MutMap, MutSet, SendMap};
use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel; 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 and Float store a variable to generate better error messages
Int(Variable, Variable, i128), Int(Variable, Variable, i128),
Float(Variable, Variable, f64), Float(Variable, Variable, f64),
Str(InlinableString), Str(Box<str>),
List { List {
elem_var: Variable, elem_var: Variable,
loc_elems: Vec<Located<Expr>>, loc_elems: Vec<Located<Expr>>,
@ -980,7 +979,7 @@ where
visited.insert(defined_symbol); visited.insert(defined_symbol);
for local in refs.lookups.iter() { for local in refs.lookups.iter() {
if !visited.contains(&local) { if !visited.contains(local) {
let other_refs: References = let other_refs: References =
references_from_local(*local, visited, refs_by_def, closures); references_from_local(*local, visited, refs_by_def, closures);
@ -991,7 +990,7 @@ where
} }
for call in refs.calls.iter() { 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); let other_refs = references_from_call(*call, visited, refs_by_def, closures);
answer = answer.union(other_refs); answer = answer.union(other_refs);
@ -1022,7 +1021,7 @@ where
visited.insert(call_symbol); visited.insert(call_symbol);
for closed_over_local in references.lookups.iter() { for closed_over_local in references.lookups.iter() {
if !visited.contains(&closed_over_local) { if !visited.contains(closed_over_local) {
let other_refs = let other_refs =
references_from_local(*closed_over_local, visited, refs_by_def, closures); references_from_local(*closed_over_local, visited, refs_by_def, closures);
@ -1033,7 +1032,7 @@ where
} }
for call in references.calls.iter() { 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); let other_refs = references_from_call(*call, visited, refs_by_def, closures);
answer = answer.union(other_refs); answer = answer.union(other_refs);
@ -1574,7 +1573,7 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
enum StrSegment { enum StrSegment {
Interpolation(Located<Expr>), Interpolation(Located<Expr>),
Plaintext(InlinableString), Plaintext(Box<str>),
} }
fn flatten_str_lines<'a>( fn flatten_str_lines<'a>(
@ -1601,10 +1600,10 @@ fn flatten_str_lines<'a>(
buf.push(ch); buf.push(ch);
} }
None => { None => {
env.problem(Problem::InvalidUnicodeCodePoint(loc_hex_digits.region)); env.problem(Problem::InvalidUnicodeCodePt(loc_hex_digits.region));
return ( return (
Expr::RuntimeError(RuntimeError::InvalidUnicodeCodePoint( Expr::RuntimeError(RuntimeError::InvalidUnicodeCodePt(
loc_hex_digits.region, loc_hex_digits.region,
)), )),
output, output,

View file

@ -47,7 +47,7 @@ pub fn canonicalize_module_defs<'a, F>(
home: ModuleId, home: ModuleId,
module_ids: &ModuleIds, module_ids: &ModuleIds,
exposed_ident_ids: IdentIds, exposed_ident_ids: IdentIds,
dep_idents: MutMap<ModuleId, IdentIds>, dep_idents: &'a MutMap<ModuleId, IdentIds>,
aliases: MutMap<Symbol, Alias>, aliases: MutMap<Symbol, Alias>,
exposed_imports: MutMap<Ident, (Symbol, Region)>, exposed_imports: MutMap<Ident, (Symbol, Region)>,
exposed_symbols: &MutSet<Symbol>, exposed_symbols: &MutSet<Symbol>,
@ -98,7 +98,7 @@ where
// Here we essentially add those "defs" to "the beginning of the module" // Here we essentially add those "defs" to "the beginning of the module"
// by canonicalizing them right before we canonicalize the actual ast::Def nodes. // by canonicalizing them right before we canonicalize the actual ast::Def nodes.
for (ident, (symbol, region)) in exposed_imports { 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() { if first_char.is_lowercase() {
// this is a value definition // 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, &mut env,
Output::default(), Output::default(),
var_store, var_store,

View file

@ -276,7 +276,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
}) })
} }
When(loc_cond_expr, branches) => { 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); let mut desugared_branches = Vec::with_capacity_in(branches.len(), arena);
for branch in branches.iter() { for branch in branches.iter() {
@ -346,7 +346,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
} }
If(if_thens, final_else_branch) => { If(if_thens, final_else_branch) => {
// If does not get desugared into `when` so we can give more targeted error messages during type checking. // 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); 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<Expr<'a>>) -> &'a
}) })
} }
Expect(condition, continuation) => { Expect(condition, continuation) => {
let desugared_condition = &*arena.alloc(desugar_expr(arena, &condition)); let desugared_condition = &*arena.alloc(desugar_expr(arena, condition));
let desugared_continuation = &*arena.alloc(desugar_expr(arena, &continuation)); let desugared_continuation = &*arena.alloc(desugar_expr(arena, continuation));
arena.alloc(Located { arena.alloc(Located {
value: Expect(desugared_condition, desugared_continuation), value: Expect(desugared_condition, desugared_continuation),
region: loc_expr.region, region: loc_expr.region,

View file

@ -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) { WhenBranch => match finish_parsing_float(string) {
Err(_error) => { Err(_error) => {
let problem = MalformedPatternProblem::MalformedFloat; let problem = MalformedPatternProblem::MalformedFloat;

View file

@ -47,7 +47,7 @@ impl Scope {
let alias = Alias { let alias = Alias {
region, region,
typ, typ,
lambda_set_variables: MutSet::default(), lambda_set_variables: Vec::new(),
recursion_variables: MutSet::default(), recursion_variables: MutSet::default(),
type_variables: variables, type_variables: variables,
}; };
@ -89,7 +89,7 @@ impl Scope {
None => Err(RuntimeError::LookupNotInScope( None => Err(RuntimeError::LookupNotInScope(
Located { Located {
region, region,
value: ident.clone().into(), value: ident.clone(),
}, },
self.idents.keys().map(|v| v.as_ref().into()).collect(), self.idents.keys().map(|v| v.as_ref().into()).collect(),
)), )),
@ -124,9 +124,9 @@ impl Scope {
// If this IdentId was already added previously // If this IdentId was already added previously
// when the value was exposed in the module header, // when the value was exposed in the module header,
// use that existing IdentId. Otherwise, create a fresh one. // 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, 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); let symbol = Symbol::new(self.home, ident_id);
@ -143,7 +143,7 @@ impl Scope {
/// ///
/// Used for record guards like { x: Just _ } /// Used for record guards like { x: Just _ }
pub fn ignore(&mut self, ident: Ident, all_ident_ids: &mut IdentIds) -> Symbol { 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) Symbol::new(self.home, ident_id)
} }
@ -198,6 +198,11 @@ impl Scope {
true true
}); });
let lambda_set_variables: Vec<_> = lambda_set_variables
.into_iter()
.map(|v| roc_types::types::LambdaSet(Type::Variable(v)))
.collect();
let alias = Alias { let alias = Alias {
region, region,
type_variables: vars, type_variables: vars,

View file

@ -313,7 +313,7 @@ pub fn canonical_string_literal<'a>(_arena: &Bump, _raw: &'a str, _region: Regio
// problems.push(Loc { // problems.push(Loc {
// region, // region,
// value: Problem::UnicodeCodePointTooLarge, // value: Problem::UnicodeCodePtTooLarge,
// }); // });
// } else { // } else {
// // If it all checked out, add it to // // 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), // Some(ch) => buf.push(ch),
// None => { // None => {
// problems.push(loc_escaped_unicode( // problems.push(loc_escaped_unicode(
// Problem::InvalidUnicodeCodePoint, // Problem::InvalidUnicodeCodePt,
// &state, // &state,
// start_of_unicode, // start_of_unicode,
// hex_str.len(), // 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() { // let problem = if hex_str.is_empty() {
// Problem::NoUnicodeDigits // Problem::NoUnicodeDigits
// } else { // } else {
// Problem::NonHexCharsInUnicodeCodePoint // Problem::NonHexCharsInUnicodeCodePt
// }; // };
// problems.push(loc_escaped_unicode( // problems.push(loc_escaped_unicode(

View file

@ -34,7 +34,7 @@ pub struct CanExprOut {
#[allow(dead_code)] #[allow(dead_code)]
pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut { 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!( panic!(
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}", "can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}",
expr_str, e 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 mut scope = Scope::new(home, &mut var_store);
let dep_idents = IdentIds::exposed_builtins(0); 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( let (loc_expr, output) = canonicalize_expr(
&mut env, &mut env,
&mut var_store, &mut var_store,

View file

@ -145,7 +145,7 @@ mod test_can {
let region = Region::zero(); let region = Region::zero();
assert_can( assert_can(
&string.clone(), string.clone(),
RuntimeError(RuntimeError::InvalidFloat( RuntimeError(RuntimeError::InvalidFloat(
FloatErrorKind::Error, FloatErrorKind::Error,
region, region,
@ -658,7 +658,7 @@ mod test_can {
recursive: recursion, recursive: recursion,
.. ..
}) => recursion.clone(), }) => recursion.clone(),
Some(other @ _) => { Some(other) => {
panic!("assignment at {} is not a closure, but a {:?}", i, other) panic!("assignment at {} is not a closure, but a {:?}", i, other)
} }
None => { None => {
@ -680,7 +680,7 @@ mod test_can {
recursive: recursion, recursive: recursion,
.. ..
} => recursion.clone(), } => recursion.clone(),
other @ _ => { other => {
panic!("assignment at {} is not a closure, but a {:?}", i, 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.) // // (Rust has this restriction. I assume it's a good idea.)
// assert_malformed_str( // assert_malformed_str(
// r#""abc\u{110000}def""#, // r#""abc\u{110000}def""#,
// vec![Located::new(0, 7, 0, 12, Problem::UnicodeCodePointTooLarge)], // vec![Located::new(0, 7, 0, 12, Problem::UnicodeCodePtTooLarge)],
// ); // );
// } // }

View file

@ -79,7 +79,7 @@ where
let mut buf = String::new_in(arena); let mut buf = String::new_in(arena);
if let Some(first) = strings.next() { if let Some(first) = strings.next() {
buf.push_str(&first); buf.push_str(first);
for string in strings { for string in strings {
buf.reserve(join_str.len() + string.len()); buf.reserve(join_str.len() + string.len());
@ -133,7 +133,7 @@ where
let mut answer = MutMap::default(); let mut answer = MutMap::default();
for (key, right_value) in map2 { for (key, right_value) in map2 {
match std::collections::HashMap::get(map1, &key) { match std::collections::HashMap::get(map1, key) {
None => (), None => (),
Some(left_value) => { Some(left_value) => {
answer.insert(key.clone(), (left_value.clone(), right_value.clone())); answer.insert(key.clone(), (left_value.clone(), right_value.clone()));

View file

@ -2,7 +2,7 @@ use roc_can::constraint::Constraint::{self, *};
use roc_can::constraint::LetConstraint; use roc_can::constraint::LetConstraint;
use roc_can::expected::Expected::{self, *}; use roc_can::expected::Expected::{self, *};
use roc_collections::all::SendMap; use roc_collections::all::SendMap;
use roc_module::ident::TagName; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::Region; use roc_region::all::Region;
use roc_types::subs::Variable; use roc_types::subs::Variable;
@ -89,9 +89,23 @@ pub fn str_type() -> Type {
builtin_type(Symbol::STR_STR, Vec::new()) builtin_type(Symbol::STR_STR, Vec::new())
} }
#[inline(always)]
fn builtin_alias(
symbol: Symbol,
type_arguments: Vec<(Lowercase, Type)>,
actual: Box<Type>,
) -> Type {
Type::Alias {
symbol,
type_arguments,
actual,
lambda_set_variables: vec![],
}
}
#[inline(always)] #[inline(always)]
pub fn num_float(range: Type) -> Type { pub fn num_float(range: Type) -> Type {
Type::Alias( builtin_alias(
Symbol::NUM_FLOAT, Symbol::NUM_FLOAT,
vec![("range".into(), range.clone())], vec![("range".into(), range.clone())],
Box::new(num_num(num_floatingpoint(range))), Box::new(num_num(num_floatingpoint(range))),
@ -108,7 +122,7 @@ pub fn num_floatingpoint(range: Type) -> Type {
Box::new(Type::EmptyTagUnion), Box::new(Type::EmptyTagUnion),
); );
Type::Alias( builtin_alias(
Symbol::NUM_FLOATINGPOINT, Symbol::NUM_FLOATINGPOINT,
vec![("range".into(), range)], vec![("range".into(), range)],
Box::new(alias_content), Box::new(alias_content),
@ -122,12 +136,12 @@ pub fn num_binary64() -> Type {
Box::new(Type::EmptyTagUnion), 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)] #[inline(always)]
pub fn num_int(range: Type) -> Type { pub fn num_int(range: Type) -> Type {
Type::Alias( builtin_alias(
Symbol::NUM_INT, Symbol::NUM_INT,
vec![("range".into(), range.clone())], vec![("range".into(), range.clone())],
Box::new(num_num(num_integer(range))), Box::new(num_num(num_integer(range))),
@ -141,7 +155,7 @@ pub fn num_signed64() -> Type {
Box::new(Type::EmptyTagUnion), 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)] #[inline(always)]
@ -154,7 +168,7 @@ pub fn num_integer(range: Type) -> Type {
Box::new(Type::EmptyTagUnion), Box::new(Type::EmptyTagUnion),
); );
Type::Alias( builtin_alias(
Symbol::NUM_INTEGER, Symbol::NUM_INTEGER,
vec![("range".into(), range)], vec![("range".into(), range)],
Box::new(alias_content), Box::new(alias_content),
@ -168,7 +182,7 @@ pub fn num_num(typ: Type) -> Type {
Box::new(Type::EmptyTagUnion), Box::new(Type::EmptyTagUnion),
); );
Type::Alias( builtin_alias(
Symbol::NUM_NUM, Symbol::NUM_NUM,
vec![("range".into(), typ)], vec![("range".into(), typ)],
Box::new(alias_content), Box::new(alias_content),

View file

@ -1121,7 +1121,7 @@ pub fn constrain_decls(home: ModuleId, decls: &[Declaration]) -> Constraint {
} }
// this assert make the "root" of the constraint wasn't dropped // 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 constraint
} }
@ -1446,7 +1446,7 @@ fn instantiate_rigids(
let mut rigid_substitution: ImMap<Variable, Type> = ImMap::default(); let mut rigid_substitution: ImMap<Variable, Type> = ImMap::default();
for (name, var) in introduced_vars.var_by_name.iter() { 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)); rigid_substitution.insert(*var, Type::Variable(*existing_rigid));
} else { } else {
// It's possible to use this rigid in nested defs // It's possible to use this rigid in nested defs

View file

@ -57,7 +57,7 @@ pub fn constrain_imported_values(
// an imported symbol can be either an alias or a value // an imported symbol can be either an alias or a value
match import.solved_type { 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 // do nothing, in the future the alias definitions should not be in the list of imported values
} }
_ => { _ => {

View file

@ -119,7 +119,7 @@ fn headers_from_annotation_help(
} }
/// This accepts PatternState (rather than returning it) so that the caller can /// 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. /// based on its knowledge of their lengths.
pub fn constrain_pattern( pub fn constrain_pattern(
env: &Env, env: &Env,
@ -206,7 +206,7 @@ pub fn constrain_pattern(
let pat_type = Type::Variable(*var); let pat_type = Type::Variable(*var);
let expected = PExpected::NoExpectation(pat_type.clone()); let expected = PExpected::NoExpectation(pat_type.clone());
if !state.headers.contains_key(&symbol) { if !state.headers.contains_key(symbol) {
state state
.headers .headers
.insert(*symbol, Located::at(region, pat_type.clone())); .insert(*symbol, Located::at(region, pat_type.clone()));

View file

@ -13,7 +13,6 @@ roc_parse = { path = "../parse" }
im = "14" # im and im-rc should always have the same version! 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! im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] } bumpalo = { version = "3.6.1", features = ["collections"] }
inlinable_string = "0.1"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1" pretty_assertions = "0.5.1"

View file

@ -295,7 +295,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
items, items,
final_comments, 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), BinOps(lefts, right) => fmt_bin_ops(buf, lefts, right, false, parens, indent),
UnaryOp(sub_expr, unary_op) => { UnaryOp(sub_expr, unary_op) => {
@ -1027,7 +1027,7 @@ fn format_field_multiline<'a, T>(
format_field_multiline(buf, sub_field, indent, separator_prefix); format_field_multiline(buf, sub_field, indent, separator_prefix);
} }
AssignedField::SpaceAfter(sub_field, spaces) => { AssignedField::SpaceAfter(sub_field, spaces) => {
// We have somethig like that: // We have something like that:
// ``` // ```
// field # comment // field # comment
// , otherfield // , otherfield

View file

@ -21,7 +21,6 @@ roc_mono = { path = "../mono" }
im = "14" # im and im-rc should always have the same version! 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! im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] } bumpalo = { version = "3.6.1", features = ["collections"] }
inlinable_string = "0.1"
target-lexicon = "0.10" target-lexicon = "0.10"
libloading = "0.6" libloading = "0.6"
object = { version = "0.24", features = ["write"] } object = { version = "0.24", features = ["write"] }

View file

@ -74,7 +74,7 @@ where
/// build_proc creates a procedure and outputs it to the wrapped object writer. /// 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> { fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> {
self.reset(); self.reset();
self.load_args(&proc.args)?; self.load_args(proc.args)?;
// let start = std::time::Instant::now(); // let start = std::time::Instant::now();
self.scan_ast(&proc.body); self.scan_ast(&proc.body);
self.create_free_map(); self.create_free_map();
@ -100,19 +100,6 @@ where
self.free_symbols(stmt); self.free_symbols(stmt);
Ok(()) 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 { Stmt::Switch {
cond_symbol, cond_symbol,
cond_layout, cond_layout,
@ -201,6 +188,12 @@ where
Symbol::NUM_SUB => { Symbol::NUM_SUB => {
self.build_run_low_level(sym, &LowLevel::NumSub, arguments, layout) 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 => { Symbol::BOOL_EQ => {
self.build_run_low_level(sym, &LowLevel::Eq, arguments, layout) self.build_run_low_level(sym, &LowLevel::Eq, arguments, layout)
} }
@ -300,7 +293,13 @@ where
// Should we panic? // Should we panic?
x => Err(format!("wrong layout, {:?}, for LowLevel::Eq", x)), 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)), x => Err(format!("low level, {:?}. is not yet implemented", x)),
} }
} }
@ -487,20 +486,6 @@ where
self.scan_ast(following); 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 { Stmt::Switch {
cond_symbol, cond_symbol,
branches, branches,
@ -516,7 +501,6 @@ where
Stmt::Ret(sym) => { Stmt::Ret(sym) => {
self.set_last_seen(*sym, stmt); self.set_last_seen(*sym, stmt);
} }
Stmt::Resume(_exception_id) => {}
Stmt::Refcounting(modify, following) => { Stmt::Refcounting(modify, following) => {
let sym = modify.get_symbol(); let sym = modify.get_symbol();

View file

@ -179,6 +179,12 @@ fn build_object<'a, B: Backend<'a>>(
"roc_dealloc".into(), "roc_dealloc".into(),
"free".into(), "free".into(),
)?; )?;
generate_wrapper(
&mut backend,
&mut output,
"roc_panic".into(),
"roc_builtins.utils.test_panic".into(),
)?;
} }
// Setup layout_ids for procedure calls. // Setup layout_ids for procedure calls.

File diff suppressed because it is too large Load diff

View file

@ -49,7 +49,7 @@ pub fn helper<'a>(
let loaded = roc_load::file::load_and_monomorphize_from_str( let loaded = roc_load::file::load_and_monomorphize_from_str(
arena, arena,
filename, filename,
&module_src, module_src,
&stdlib, &stdlib,
src_dir, src_dir,
exposed_types, exposed_types,
@ -73,19 +73,22 @@ pub fn helper<'a>(
procedures.insert(key, proc); procedures.insert(key, proc);
} }
/* // You can comment and uncomment this block out to get more useful information
println!("=========== Procedures =========="); // while you're working on the dev backend!
println!("{:?}", procedures); {
println!("=================================\n"); // println!("=========== Procedures ==========");
// println!("{:?}", procedures);
// println!("=================================\n");
println!("=========== Interns =========="); // println!("=========== Interns ==========");
println!("{:?}", interns); // println!("{:?}", interns);
println!("=================================\n"); // 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); debug_assert_eq!(exposed_to_host.len(), 1);
let main_fn_symbol = loaded.entry_point.symbol; let main_fn_symbol = loaded.entry_point.symbol;
let main_fn_layout = loaded.entry_point.layout; let main_fn_layout = loaded.entry_point.layout;

View file

@ -20,7 +20,6 @@ morphic_lib = { path = "../../vendor/morphic_lib" }
im = "14" # im and im-rc should always have the same version! 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! im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] } bumpalo = { version = "3.6.1", features = ["collections"] }
inlinable_string = "0.1"
inkwell = { path = "../../vendor/inkwell" } inkwell = { path = "../../vendor/inkwell" }
target-lexicon = "0.10" target-lexicon = "0.10"

View file

@ -78,7 +78,7 @@ pub fn build_has_tag_id<'a, 'ctx, 'env>(
match env.module.get_function(fn_name) { match env.module.get_function(fn_name) {
Some(function_value) => function_value, 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( let function_value = crate::llvm::refcounting::build_header_help(
env, env,
&fn_name, fn_name,
output_type.into(), output_type.into(),
&argument_types, argument_types,
); );
// called from zig, must use C calling convention // 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); bumpalo::collections::Vec::from_iter_in(it.take(argument_types.len()), env.arena);
for (argument, name) in arguments.iter().zip(ARGUMENT_SYMBOLS.iter()) { 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() { match arguments.as_slice() {
@ -204,7 +204,7 @@ pub fn build_transform_caller<'a, 'ctx, 'env>(
function, function,
closure_data_layout, closure_data_layout,
argument_layouts, 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( let function_value = crate::llvm::refcounting::build_header_help(
env, env,
&fn_name, fn_name,
env.context.void_type().into(), env.context.void_type().into(),
&(bumpalo::vec![ in env.arena; BasicTypeEnum::PointerType(arg_type); argument_layouts.len() + 2 ]), &(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 mut it = function_value.get_param_iter();
let closure_ptr = it.next().unwrap().into_pointer_value(); 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 = let arguments =
bumpalo::collections::Vec::from_iter_in(it.take(argument_layouts.len()), env.arena); bumpalo::collections::Vec::from_iter_in(it.take(argument_layouts.len()), env.arena);
for (argument, name) in arguments.iter().zip(ARGUMENT_SYMBOLS[1..].iter()) { 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 = let mut arguments_cast =
@ -271,13 +271,11 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
} }
match closure_data_layout { match closure_data_layout {
Layout::Closure(_, lambda_set, _) => { Layout::Struct(&[]) => {
if let Layout::Struct(&[]) = lambda_set.runtime_representation() { // nothing to add
// do nothing }
} else { other => {
let closure_type = let closure_type = basic_type_from_layout(env, &other).ptr_type(AddressSpace::Generic);
basic_type_from_layout(env, &lambda_set.runtime_representation())
.ptr_type(AddressSpace::Generic);
let closure_cast = env let closure_cast = env
.builder .builder
@ -289,33 +287,6 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
arguments_cast.push(closure_data); arguments_cast.push(closure_data);
} }
} }
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);
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([]) => {
// 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 = { let call = {
env.builder env.builder
@ -394,7 +365,7 @@ fn build_rc_wrapper<'a, 'ctx, 'env>(
let symbol = Symbol::GENERIC_RC_REF; let symbol = Symbol::GENERIC_RC_REF;
let fn_name = layout_ids let fn_name = layout_ids
.get(symbol, &layout) .get(symbol, layout)
.to_symbol_string(symbol, &env.interns); .to_symbol_string(symbol, &env.interns);
let fn_name = match rc_operation { 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 mut it = function_value.get_param_iter();
let value_ptr = it.next().unwrap().into_pointer_value(); 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); 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 => { Mode::IncN => {
let n = it.next().unwrap().into_int_value(); 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); 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 symbol = Symbol::GENERIC_EQ_REF;
let fn_name = layout_ids let fn_name = layout_ids
.get(symbol, &layout) .get(symbol, layout)
.to_symbol_string(symbol, &env.interns); .to_symbol_string(symbol, &env.interns);
let function_value = match env.module.get_function(fn_name.as_str()) { 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_ptr1 = it.next().unwrap().into_pointer_value();
let value_ptr2 = 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_ptr1.set_name(Symbol::ARG_1.as_str(&env.interns));
value_ptr2.set_name(Symbol::ARG_2.ident_string(&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); 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( let function_value = crate::llvm::refcounting::build_header_help(
env, env,
&fn_name, fn_name,
env.context.i8_type().into(), env.context.i8_type().into(),
&[arg_type.into(), arg_type.into(), arg_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_ptr1 = it.next().unwrap().into_pointer_value();
let value_ptr2 = 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)); closure_ptr.set_name(Symbol::ARG_1.as_str(&env.interns));
value_ptr1.set_name(Symbol::ARG_2.ident_string(&env.interns)); value_ptr1.set_name(Symbol::ARG_2.as_str(&env.interns));
value_ptr2.set_name(Symbol::ARG_3.ident_string(&env.interns)); value_ptr2.set_name(Symbol::ARG_3.as_str(&env.interns));
let value_type = basic_type_from_layout(env, layout); let value_type = basic_type_from_layout(env, layout);
let value_ptr_type = value_type.ptr_type(AddressSpace::Generic); let value_ptr_type = value_type.ptr_type(AddressSpace::Generic);
@ -625,13 +596,13 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
let default = [value1, value2]; let default = [value1, value2];
let arguments_cast = match closure_data_layout { let arguments_cast = match closure_data_layout {
Layout::Closure(_, lambda_set, _) => { Layout::Struct(&[]) => {
if let Layout::Struct(&[]) = lambda_set.runtime_representation() { // nothing to add
&default &default
} else { }
other => {
let closure_type = let closure_type =
basic_type_from_layout(env, &lambda_set.runtime_representation()) basic_type_from_layout(env, &other).ptr_type(AddressSpace::Generic);
.ptr_type(AddressSpace::Generic);
let closure_cast = env let closure_cast = env
.builder .builder
@ -642,9 +613,6 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
env.arena.alloc([value1, value2, closure_data]) as &[_] 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( let call = env.builder.build_call(

File diff suppressed because it is too large Load diff

View file

@ -64,7 +64,7 @@ pub fn dict_len<'a, 'ctx, 'env>(
.build_alloca(dict_as_zig_dict.get_type(), "dict_ptr"); .build_alloca(dict_as_zig_dict.get_type(), "dict_ptr");
env.builder.build_store(dict_ptr, dict_as_zig_dict); 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(), Layout::Builtin(Builtin::EmptyDict) => ctx.i64_type().const_zero().into(),
_ => unreachable!("Invalid layout given to Dict.len : {:?}", dict_layout), _ => 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 // 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"); 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") 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(), dec_value_fn.as_global_value().as_pointer_value().into(),
result_ptr.into(), result_ptr.into(),
], ],
&bitcode::DICT_INSERT, bitcode::DICT_INSERT,
); );
env.builder.build_load(result_ptr, "load_result") 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(), dec_value_fn.as_global_value().as_pointer_value().into(),
result_ptr.into(), result_ptr.into(),
], ],
&bitcode::DICT_REMOVE, bitcode::DICT_REMOVE,
); );
env.builder.build_load(result_ptr, "load_result") 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(), hash_fn.as_global_value().as_pointer_value().into(),
eq_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(), eq_fn.as_global_value().as_pointer_value().into(),
inc_value_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(); .into_struct_value();
@ -415,7 +415,7 @@ pub fn dict_elements_rc<'a, 'ctx, 'env>(
key_fn.as_global_value().as_pointer_value().into(), key_fn.as_global_value().as_pointer_value().into(),
value_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(), inc_key_fn.as_global_value().as_pointer_value().into(),
list_ptr.into(), list_ptr.into(),
], ],
&bitcode::DICT_KEYS, bitcode::DICT_KEYS,
); );
let list_ptr = env 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(), inc_value_fn.as_global_value().as_pointer_value().into(),
output_ptr.into(), output_ptr.into(),
], ],
&bitcode::DICT_UNION, bitcode::DICT_UNION,
); );
env.builder.build_load(output_ptr, "load_output_ptr") env.builder.build_load(output_ptr, "load_output_ptr")
@ -549,7 +549,7 @@ pub fn dict_difference<'a, 'ctx, 'env>(
dict2, dict2,
key_layout, key_layout,
value_layout, value_layout,
&bitcode::DICT_DIFFERENCE, bitcode::DICT_DIFFERENCE,
) )
} }
@ -569,7 +569,7 @@ pub fn dict_intersection<'a, 'ctx, 'env>(
dict2, dict2,
key_layout, key_layout,
value_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), layout_width(env, accum_layout),
env.builder.build_bitcast(output_ptr, u8_ptr, "to_opaque"), 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") 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(), inc_value_fn.as_global_value().as_pointer_value().into(),
list_ptr.into(), list_ptr.into(),
], ],
&bitcode::DICT_VALUES, bitcode::DICT_VALUES,
); );
let list_ptr = env 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(), dec_key_fn.as_global_value().as_pointer_value().into(),
result_alloca.into(), result_alloca.into(),
], ],
&bitcode::SET_FROM_LIST, bitcode::SET_FROM_LIST,
); );
env.builder.build_load(result_alloca, "load_result") 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 symbol = Symbol::GENERIC_HASH_REF;
let fn_name = layout_ids let fn_name = layout_ids
.get(symbol, &layout) .get(symbol, layout)
.to_symbol_string(symbol, &env.interns); .to_symbol_string(symbol, &env.interns);
let function_value = match env.module.get_function(fn_name.as_str()) { 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 seed_arg = it.next().unwrap().into_int_value();
let value_ptr = it.next().unwrap().into_pointer_value(); let value_ptr = it.next().unwrap().into_pointer_value();
seed_arg.set_name(Symbol::ARG_1.ident_string(&env.interns)); seed_arg.set_name(Symbol::ARG_1.as_str(&env.interns));
value_ptr.set_name(Symbol::ARG_2.ident_string(&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); 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(); 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") complex_bitcast(env.builder, dict, zig_dict_type.into(), "dict_to_zig_dict").into_struct_value()
.into_struct_value()
} }
fn zig_dict_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> inkwell::types::StructType<'ctx> { fn zig_dict_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> inkwell::types::StructType<'ctx> {

View file

@ -1,5 +1,6 @@
use crate::debug_info_init; use crate::debug_info_init;
use crate::llvm::bitcode::call_bitcode_fn; 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::Env;
use crate::llvm::build::{cast_block_of_memory_to_tag, get_tag_id, FAST_CALL_CONV, TAG_DATA_INDEX}; use crate::llvm::build::{cast_block_of_memory_to_tag, get_tag_id, FAST_CALL_CONV, TAG_DATA_INDEX};
use crate::llvm::build_str; 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::Float32
| Builtin::Float128 | Builtin::Float128
| Builtin::Float16 | Builtin::Float16
| Builtin::Decimal
| Builtin::Usize => { | 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)) hash_bitcode_fn(env, seed, hash_bytes, layout.stack_size(ptr_bytes))
} }
Builtin::Str => { Builtin::Str => {
@ -136,7 +134,7 @@ fn hash_builtin<'a, 'ctx, 'env>(
call_bitcode_fn( call_bitcode_fn(
env, env,
&[seed.into(), build_str::str_to_i128(env, val).into()], &[seed.into(), build_str::str_to_i128(env, val).into()],
&bitcode::DICT_HASH_STR, bitcode::DICT_HASH_STR,
) )
.into_int_value() .into_int_value()
} }
@ -234,8 +232,8 @@ fn build_hash_struct_help<'a, 'ctx, 'env>(
let seed = it.next().unwrap().into_int_value(); let seed = it.next().unwrap().into_int_value();
let value = it.next().unwrap().into_struct_value(); let value = it.next().unwrap().into_struct_value();
seed.set_name(Symbol::ARG_1.ident_string(&env.interns)); seed.set_name(Symbol::ARG_1.as_str(&env.interns));
value.set_name(Symbol::ARG_2.ident_string(&env.interns)); value.set_name(Symbol::ARG_2.as_str(&env.interns));
let entry = ctx.append_basic_block(parent, "entry"); let entry = ctx.append_basic_block(parent, "entry");
env.builder.position_at_end(entry); env.builder.position_at_end(entry);
@ -325,7 +323,7 @@ fn build_hash_tag<'a, 'ctx, 'env>(
let symbol = Symbol::GENERIC_HASH; let symbol = Symbol::GENERIC_HASH;
let fn_name = layout_ids let fn_name = layout_ids
.get(symbol, &layout) .get(symbol, layout)
.to_symbol_string(symbol, &env.interns); .to_symbol_string(symbol, &env.interns);
let function = match env.module.get_function(fn_name.as_str()) { let function = match env.module.get_function(fn_name.as_str()) {
@ -333,7 +331,7 @@ fn build_hash_tag<'a, 'ctx, 'env>(
None => { None => {
let seed_type = env.context.i64_type(); 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( let function_value = crate::llvm::refcounting::build_header_help(
env, env,
@ -375,8 +373,8 @@ fn build_hash_tag_help<'a, 'ctx, 'env>(
let seed = it.next().unwrap().into_int_value(); let seed = it.next().unwrap().into_int_value();
let value = it.next().unwrap(); let value = it.next().unwrap();
seed.set_name(Symbol::ARG_1.ident_string(&env.interns)); seed.set_name(Symbol::ARG_1.as_str(&env.interns));
value.set_name(Symbol::ARG_2.ident_string(&env.interns)); value.set_name(Symbol::ARG_2.as_str(&env.interns));
let entry = ctx.append_basic_block(parent, "entry"); let entry = ctx.append_basic_block(parent, "entry");
env.builder.position_at_end(entry); env.builder.position_at_end(entry);
@ -493,14 +491,9 @@ fn hash_tag<'a, 'ctx, 'env>(
); );
// hash the tag data // hash the tag data
let answer = hash_ptr_to_struct( let tag = tag_pointer_clear_tag_id(env, tag.into_pointer_value());
env, let answer =
layout_ids, hash_ptr_to_struct(env, layout_ids, union_layout, field_layouts, seed, tag);
union_layout,
field_layouts,
seed,
tag.into_pointer_value(),
);
merge_phi.add_incoming(&[(&answer, block)]); merge_phi.add_incoming(&[(&answer, block)]);
env.builder.build_unconditional_branch(merge_block); env.builder.build_unconditional_branch(merge_block);
@ -598,6 +591,7 @@ fn hash_tag<'a, 'ctx, 'env>(
); );
// hash tag data // hash tag data
let tag = tag_pointer_clear_tag_id(env, tag);
let answer = hash_ptr_to_struct( let answer = hash_ptr_to_struct(
env, env,
layout_ids, layout_ids,
@ -661,7 +655,7 @@ fn build_hash_list<'a, 'ctx, 'env>(
let symbol = Symbol::GENERIC_HASH; let symbol = Symbol::GENERIC_HASH;
let fn_name = layout_ids let fn_name = layout_ids
.get(symbol, &layout) .get(symbol, layout)
.to_symbol_string(symbol, &env.interns); .to_symbol_string(symbol, &env.interns);
let function = match env.module.get_function(fn_name.as_str()) { let function = match env.module.get_function(fn_name.as_str()) {
@ -669,7 +663,7 @@ fn build_hash_list<'a, 'ctx, 'env>(
None => { None => {
let seed_type = env.context.i64_type(); 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( let function_value = crate::llvm::refcounting::build_header_help(
env, env,
@ -718,8 +712,8 @@ fn build_hash_list_help<'a, 'ctx, 'env>(
let seed = it.next().unwrap().into_int_value(); let seed = it.next().unwrap().into_int_value();
let value = it.next().unwrap().into_struct_value(); let value = it.next().unwrap().into_struct_value();
seed.set_name(Symbol::ARG_1.ident_string(&env.interns)); seed.set_name(Symbol::ARG_1.as_str(&env.interns));
value.set_name(Symbol::ARG_2.ident_string(&env.interns)); value.set_name(Symbol::ARG_2.as_str(&env.interns));
let entry = ctx.append_basic_block(parent, "entry"); let entry = ctx.append_basic_block(parent, "entry");
env.builder.position_at_end(entry); env.builder.position_at_end(entry);
@ -872,7 +866,7 @@ fn store_and_use_as_u8_ptr<'a, 'ctx, 'env>(
value: BasicValueEnum<'ctx>, value: BasicValueEnum<'ctx>,
layout: &Layout<'a>, layout: &Layout<'a>,
) -> PointerValue<'ctx> { ) -> 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"); let alloc = env.builder.build_alloca(basic_type, "store");
env.builder.build_store(alloc, value); env.builder.build_store(alloc, value);
@ -897,7 +891,7 @@ fn hash_bitcode_fn<'a, 'ctx, 'env>(
call_bitcode_fn( call_bitcode_fn(
env, env,
&[seed.into(), buffer.into(), num_bytes.into()], &[seed.into(), buffer.into(), num_bytes.into()],
&bitcode::DICT_HASH, bitcode::DICT_HASH,
) )
.into_int_value() .into_int_value()
} }

View file

@ -94,7 +94,7 @@ pub fn list_single<'a, 'ctx, 'env>(
pass_element_as_opaque(env, element), pass_element_as_opaque(env, element),
layout_width(env, element_layout), 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 /// List.join : List (List elem) -> List elem
pub fn list_join<'a, 'ctx, 'env>( pub fn list_join<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
@ -207,7 +143,7 @@ pub fn list_join<'a, 'ctx, 'env>(
env.alignment_intvalue(element_layout), env.alignment_intvalue(element_layout),
layout_width(env, 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), env.alignment_intvalue(&element_layout),
layout_width(env, &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, env,
&[ &[
pass_list_as_i128(env, original_wrapper.into()), pass_list_as_i128(env, original_wrapper.into()),
env.alignment_intvalue(&element_layout), env.alignment_intvalue(element_layout),
pass_element_as_opaque(env, element), pass_element_as_opaque(env, element),
layout_width(env, element_layout), 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, env,
&[ &[
pass_list_as_i128(env, original_wrapper.into()), pass_list_as_i128(env, original_wrapper.into()),
env.alignment_intvalue(&element_layout), env.alignment_intvalue(element_layout),
layout_width(env, &element_layout), layout_width(env, element_layout),
index_1.into(), index_1.into(),
index_2.into(), index_2.into(),
], ],
&bitcode::LIST_SWAP, bitcode::LIST_SWAP,
) )
} }
@ -329,17 +284,17 @@ pub fn list_drop<'a, 'ctx, 'env>(
count: IntValue<'ctx>, count: IntValue<'ctx>,
element_layout: &Layout<'a>, element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> { ) -> 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( call_bitcode_fn_returns_list(
env, env,
&[ &[
pass_list_as_i128(env, original_wrapper.into()), pass_list_as_i128(env, original_wrapper.into()),
env.alignment_intvalue(&element_layout), env.alignment_intvalue(element_layout),
layout_width(env, &element_layout), layout_width(env, element_layout),
count.into(), count.into(),
dec_element_fn.as_global_value().as_pointer_value().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(), bytes.into(),
length.into(), length.into(),
env.alignment_intvalue(&element_layout), env.alignment_intvalue(element_layout),
index.into(), index.into(),
pass_element_as_opaque(env, element), pass_element_as_opaque(env, element),
layout_width(env, element_layout), 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.inc_n_data.into(),
roc_function_call.data_is_owned.into(), roc_function_call.data_is_owned.into(),
pass_as_opaque(env, default_ptr), pass_as_opaque(env, default_ptr),
env.alignment_intvalue(&element_layout), env.alignment_intvalue(element_layout),
layout_width(env, element_layout), layout_width(env, element_layout),
layout_width(env, default_layout), layout_width(env, default_layout),
pass_as_opaque(env, result_ptr), 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.inc_n_data.into(),
roc_function_call.data_is_owned.into(), roc_function_call.data_is_owned.into(),
pass_as_opaque(env, default_ptr), pass_as_opaque(env, default_ptr),
env.alignment_intvalue(&element_layout), env.alignment_intvalue(element_layout),
layout_width(env, element_layout), layout_width(env, element_layout),
layout_width(env, function_call_return_layout), layout_width(env, function_call_return_layout),
layout_width(env, default_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, low_ptr),
pass_as_opaque(env, high_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), pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(), roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.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, element_layout),
inc_element_fn.as_global_value().as_pointer_value().into(), inc_element_fn.as_global_value().as_pointer_value().into(),
dec_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), pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(), roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.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, before_layout),
layout_width(env, result_layout), layout_width(env, result_layout),
layout_width(env, after_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), pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(), roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.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, before_layout),
layout_width(env, result_layout), layout_width(env, result_layout),
layout_width(env, after_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), pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(), roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.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, element_layout),
], ],
bitcode::LIST_SORT_WITH, 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), pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(), roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.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, element_layout),
layout_width(env, return_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), pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(), roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.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, element_layout),
layout_width(env, return_layout), layout_width(env, return_layout),
], ],
@ -874,7 +829,7 @@ pub fn list_concat<'a, 'ctx, 'env>(
env.alignment_intvalue(elem_layout), env.alignment_intvalue(elem_layout),
layout_width(env, elem_layout), layout_width(env, elem_layout),
], ],
&bitcode::LIST_CONCAT, bitcode::LIST_CONCAT,
), ),
_ => { _ => {
unreachable!("Invalid List layout for List.concat {:?}", list_layout); unreachable!("Invalid List layout for List.concat {:?}", list_layout);

View file

@ -27,7 +27,7 @@ pub fn str_split<'a, 'ctx, 'env>(
let segment_count = call_bitcode_fn( let segment_count = call_bitcode_fn(
env, env,
&[str_i128.into(), delim_i128.into()], &[str_i128.into(), delim_i128.into()],
&bitcode::STR_COUNT_SEGMENTS, bitcode::STR_COUNT_SEGMENTS,
) )
.into_int_value(); .into_int_value();
@ -47,7 +47,7 @@ pub fn str_split<'a, 'ctx, 'env>(
call_void_bitcode_fn( call_void_bitcode_fn(
env, env,
&[ret_list_ptr_zig_rocstr, str_i128.into(), delim_i128.into()], &[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) 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(); 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>( pub fn str_to_i128<'a, 'ctx, 'env>(
@ -119,7 +119,7 @@ pub fn str_concat<'a, 'ctx, 'env>(
call_bitcode_fn( call_bitcode_fn(
env, env,
&[str1_i128.into(), str2_i128.into()], &[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( call_bitcode_fn(
env, env,
&[list_i128.into(), str_i128.into()], &[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 // the builtin will always return an u64
let length = 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 // cast to the appropriate usize of the current build
env.builder env.builder
@ -171,11 +171,11 @@ pub fn str_starts_with<'a, 'ctx, 'env>(
call_bitcode_fn( call_bitcode_fn(
env, env,
&[str_i128.into(), prefix_i128.into()], &[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>( pub fn str_starts_with_code_point<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>, scope: &Scope<'a, 'ctx>,
@ -188,7 +188,7 @@ pub fn str_starts_with_code_point<'a, 'ctx, 'env>(
call_bitcode_fn( call_bitcode_fn(
env, env,
&[str_i128.into(), prefix], &[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( call_bitcode_fn(
env, env,
&[str_i128.into(), prefix_i128.into()], &[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( call_bitcode_fn(
env, env,
&[str_i128.into()], &[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> { ) -> BasicValueEnum<'ctx> {
let int = load_symbol(scope, &int_symbol); 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 /// Str.toUtf8 : Str -> List U8
pub fn str_to_bytes<'a, 'ctx, 'env>( pub fn str_to_utf8<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
original_wrapper: StructValue<'ctx>, original_wrapper: StructValue<'ctx>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
@ -244,10 +244,66 @@ pub fn str_to_bytes<'a, 'ctx, 'env>(
env.builder, env.builder,
original_wrapper.into(), original_wrapper.into(),
env.context.i128_type().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 } /// 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(), result_ptr.into(),
], ],
&bitcode::STR_FROM_UTF8, bitcode::STR_FROM_UTF8,
); );
let record_type = env.context.struct_type( let record_type = env.context.struct_type(
@ -306,7 +362,7 @@ pub fn str_from_float<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let float = load_symbol(scope, &int_symbol); 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 /// Str.equal : Str, Str -> Bool
@ -321,7 +377,7 @@ pub fn str_equal<'a, 'ctx, 'env>(
call_bitcode_fn( call_bitcode_fn(
env, env,
&[str1_i128.into(), str2_i128.into()], &[str1_i128.into(), str2_i128.into()],
&bitcode::STR_EQUAL, bitcode::STR_EQUAL,
) )
} }

View file

@ -1,5 +1,7 @@
use crate::llvm::build::Env; use crate::llvm::bitcode::call_bitcode_fn;
use crate::llvm::build::{cast_block_of_memory_to_tag, get_tag_id, FAST_CALL_CONV}; 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_list::{list_len, load_list_ptr};
use crate::llvm::build_str::str_equal; use crate::llvm::build_str::str_equal;
use crate::llvm::convert::basic_type_from_layout; use crate::llvm::convert::basic_type_from_layout;
@ -9,6 +11,7 @@ use inkwell::values::{
BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue, BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue,
}; };
use inkwell::{AddressSpace, FloatPredicate, IntPredicate}; use inkwell::{AddressSpace, FloatPredicate, IntPredicate};
use roc_builtins::bitcode;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout}; 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::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::Float128 => float_cmp(FloatPredicate::OEQ, "eq_f128"),
Builtin::Float64 => float_cmp(FloatPredicate::OEQ, "eq_f64"), Builtin::Float64 => float_cmp(FloatPredicate::OEQ, "eq_f64"),
Builtin::Float32 => float_cmp(FloatPredicate::OEQ, "eq_f32"), 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::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::Float128 => float_cmp(FloatPredicate::ONE, "neq_f128"),
Builtin::Float64 => float_cmp(FloatPredicate::ONE, "neq_f64"), Builtin::Float64 => float_cmp(FloatPredicate::ONE, "neq_f64"),
Builtin::Float32 => float_cmp(FloatPredicate::ONE, "neq_f32"), Builtin::Float32 => float_cmp(FloatPredicate::ONE, "neq_f32"),
@ -335,10 +336,6 @@ fn build_neq<'a, 'ctx, 'env>(
Layout::RecursivePointer => { Layout::RecursivePointer => {
unreachable!("recursion pointers should never be compared directly") 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 symbol = Symbol::LIST_EQ;
let fn_name = layout_ids let fn_name = layout_ids
.get(symbol, &element_layout) .get(symbol, element_layout)
.to_symbol_string(symbol, &env.interns); .to_symbol_string(symbol, &env.interns);
let function = match env.module.get_function(fn_name.as_str()) { let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value, Some(function_value) => function_value,
None => { 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( let function_value = crate::llvm::refcounting::build_header_help(
env, env,
@ -423,7 +420,7 @@ fn build_list_eq_help<'a, 'ctx, 'env>(
/* current_scope */ lexical_block.as_debug_info_scope(), /* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None, /* inlined_at */ None,
); );
builder.set_current_debug_location(&ctx, loc); builder.set_current_debug_location(ctx, loc);
} }
// Add args to scope // 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 list1 = it.next().unwrap().into_struct_value();
let list2 = it.next().unwrap().into_struct_value(); let list2 = it.next().unwrap().into_struct_value();
list1.set_name(Symbol::ARG_1.ident_string(&env.interns)); list1.set_name(Symbol::ARG_1.as_str(&env.interns));
list2.set_name(Symbol::ARG_2.ident_string(&env.interns)); list2.set_name(Symbol::ARG_2.as_str(&env.interns));
let entry = ctx.append_basic_block(parent, "entry"); let entry = ctx.append_basic_block(parent, "entry");
env.builder.position_at_end(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(), /* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None, /* inlined_at */ None,
); );
builder.set_current_debug_location(&ctx, loc); builder.set_current_debug_location(ctx, loc);
} }
// Add args to scope // 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 struct1 = it.next().unwrap().into_struct_value();
let struct2 = it.next().unwrap().into_struct_value(); let struct2 = it.next().unwrap().into_struct_value();
struct1.set_name(Symbol::ARG_1.ident_string(&env.interns)); struct1.set_name(Symbol::ARG_1.as_str(&env.interns));
struct2.set_name(Symbol::ARG_2.ident_string(&env.interns)); struct2.set_name(Symbol::ARG_2.as_str(&env.interns));
let entry = ctx.append_basic_block(parent, "entry"); let entry = ctx.append_basic_block(parent, "entry");
let start = ctx.append_basic_block(parent, "start"); 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 symbol = Symbol::GENERIC_EQ;
let fn_name = layout_ids let fn_name = layout_ids
.get(symbol, &tag_layout) .get(symbol, tag_layout)
.to_symbol_string(symbol, &env.interns); .to_symbol_string(symbol, &env.interns);
let function = match env.module.get_function(fn_name.as_str()) { let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value, Some(function_value) => function_value,
None => { 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( let function_value = crate::llvm::refcounting::build_header_help(
env, env,
@ -812,7 +809,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
/* current_scope */ lexical_block.as_debug_info_scope(), /* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None, /* inlined_at */ None,
); );
builder.set_current_debug_location(&ctx, loc); builder.set_current_debug_location(ctx, loc);
} }
// Add args to scope // Add args to scope
@ -820,8 +817,8 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
let tag1 = it.next().unwrap(); let tag1 = it.next().unwrap();
let tag2 = it.next().unwrap(); let tag2 = it.next().unwrap();
tag1.set_name(Symbol::ARG_1.ident_string(&env.interns)); tag1.set_name(Symbol::ARG_1.as_str(&env.interns));
tag2.set_name(Symbol::ARG_2.ident_string(&env.interns)); tag2.set_name(Symbol::ARG_2.as_str(&env.interns));
let entry = ctx.append_basic_block(parent, "entry"); 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 id1 = get_tag_id(env, parent, union_layout, tag1);
let id2 = get_tag_id(env, parent, union_layout, tag2); 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 compare_tag_fields = ctx.append_basic_block(parent, "compare_tag_fields");
let same_tag = 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"); let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block); env.builder.position_at_end(block);
let answer = eq_ptr_to_struct( let answer =
env, eq_ptr_to_struct(env, layout_ids, union_layout, field_layouts, tag1, tag2);
layout_ids,
union_layout,
field_layouts,
tag1.into_pointer_value(),
tag2.into_pointer_value(),
);
env.builder.build_return(Some(&answer)); 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 id1 = get_tag_id(env, parent, union_layout, tag1);
let id2 = get_tag_id(env, parent, union_layout, tag2); 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 compare_tag_fields = ctx.append_basic_block(parent, "compare_tag_fields");
let same_tag = 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"); let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block); env.builder.position_at_end(block);
let answer = eq_ptr_to_struct( let answer =
env, eq_ptr_to_struct(env, layout_ids, union_layout, field_layouts, tag1, tag2);
layout_ids,
union_layout,
field_layouts,
tag1.into_pointer_value(),
tag2.into_pointer_value(),
);
env.builder.build_return(Some(&answer)); env.builder.build_return(Some(&answer));

View file

@ -26,49 +26,41 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
use Layout::*; use Layout::*;
match 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), Struct(sorted_fields) => basic_type_from_record(env, sorted_fields),
Union(variant) => { Union(union_layout) => {
use UnionLayout::*; 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 { match union_layout {
NullableWrapped { 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, .. other_tags: tags, ..
} => { } => {
let data = block_of_memory_slices(env.context, tags, env.ptr_bytes); let data = block_of_memory_slices(env.context, tags, env.ptr_bytes);
if union_layout.stores_tag_id_as_data(env.ptr_bytes) {
env.context env.context
.struct_type(&[data, tag_id_type], false) .struct_type(&[data, tag_id_type], false)
.ptr_type(AddressSpace::Generic) .ptr_type(AddressSpace::Generic)
.into() .into()
} else {
data.ptr_type(AddressSpace::Generic).into()
}
} }
NullableUnwrapped { other_fields, .. } => { NullableUnwrapped { other_fields, .. } => {
let block = let block = block_of_memory_slices(env.context, &[other_fields], env.ptr_bytes);
block_of_memory_slices(env.context, &[&other_fields], env.ptr_bytes);
block.ptr_type(AddressSpace::Generic).into() block.ptr_type(AddressSpace::Generic).into()
} }
NonNullableUnwrapped(fields) => { NonNullableUnwrapped(fields) => {
let block = block_of_memory_slices(env.context, &[fields], env.ptr_bytes); let block = block_of_memory_slices(env.context, &[fields], env.ptr_bytes);
block.ptr_type(AddressSpace::Generic).into() 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 => { RecursivePointer => {
@ -100,6 +92,7 @@ pub fn basic_type_from_builtin<'a, 'ctx, 'env>(
Int8 => context.i8_type().as_basic_type_enum(), Int8 => context.i8_type().as_basic_type_enum(),
Int1 => context.bool_type().as_basic_type_enum(), Int1 => context.bool_type().as_basic_type_enum(),
Usize => ptr_int(context, ptr_bytes).as_basic_type_enum(), 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(), Float128 => context.f128_type().as_basic_type_enum(),
Float64 => context.f64_type().as_basic_type_enum(), Float64 => context.f64_type().as_basic_type_enum(),
Float32 => context.f32_type().as_basic_type_enum(), Float32 => context.f32_type().as_basic_type_enum(),
@ -145,16 +138,6 @@ pub fn union_data_is_struct_type<'ctx>(
context.struct_type(&[struct_type.into(), tag_id_type.into()], false) 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>( pub fn block_of_memory<'ctx>(
context: &'ctx Context, context: &'ctx Context,
layout: &Layout<'_>, layout: &Layout<'_>,
@ -234,3 +217,11 @@ pub fn zig_has_tag_id_type<'a, 'ctx, 'env>(
) -> StructType<'ctx> { ) -> StructType<'ctx> {
env.module.get_struct_type("list.HasTagId").unwrap() 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()
}

View file

@ -1,19 +1,18 @@
use crate::llvm::build::Env;
use crate::llvm::build::{add_func, C_CALL_CONV}; use crate::llvm::build::{add_func, C_CALL_CONV};
use crate::llvm::convert::ptr_int; use crate::llvm::convert::ptr_int;
use inkwell::builder::Builder; use inkwell::module::Linkage;
use inkwell::context::Context;
use inkwell::module::{Linkage, Module};
use inkwell::values::BasicValue; use inkwell::values::BasicValue;
use inkwell::AddressSpace; use inkwell::AddressSpace;
/// Define functions for roc_alloc, roc_realloc, and roc_dealloc /// Define functions for roc_alloc, roc_realloc, and roc_dealloc
/// which use libc implementations (malloc, realloc, and free) /// which use libc implementations (malloc, realloc, and free)
pub fn add_default_roc_externs<'ctx>( pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) {
ctx: &'ctx Context, let ctx = env.context;
module: &Module<'ctx>, let module = env.module;
builder: &Builder<'ctx>, let builder = env.builder;
ptr_bytes: u32, let ptr_bytes = env.ptr_bytes;
) {
let usize_type = ptr_int(ctx, ptr_bytes); let usize_type = ptr_int(ctx, ptr_bytes);
let i8_ptr_type = ctx.i8_type().ptr_type(AddressSpace::Generic); 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); 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);
}
}
} }

View file

@ -1,12 +1,10 @@
use crate::debug_info_init; use crate::debug_info_init;
use crate::llvm::build::{ use crate::llvm::build::{
add_func, cast_basic_basic, cast_block_of_memory_to_tag, get_tag_id, get_tag_id_non_recursive, 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::build_list::{incrementing_elem_loop, list_len, load_list};
use crate::llvm::convert::{ use crate::llvm::convert::{basic_type_from_layout, ptr_int};
basic_type_from_layout, block_of_memory_slices, ptr_int, union_data_block_of_memory,
};
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use inkwell::basic_block::BasicBlock; use inkwell::basic_block::BasicBlock;
use inkwell::context::Context; use inkwell::context::Context;
@ -280,7 +278,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
// build then block // build then block
{ {
builder.position_at_end(then_block); builder.position_at_end(then_block);
if !env.leak { if !env.is_gen_test {
let ptr = builder.build_pointer_cast( let ptr = builder.build_pointer_cast(
refcount_ptr.value, refcount_ptr.value,
ctx.i8_type().ptr_type(AddressSpace::Generic), 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_symbol = Symbol::ARG_1;
let arg_val = fn_val.get_param_iter().next().unwrap(); 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 parent = fn_val;
@ -644,89 +642,23 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
Union(variant) => { Union(variant) => {
use UnionLayout::*; use UnionLayout::*;
match variant { if let NonRecursive(tags) = variant {
NullableWrapped { let function = modify_refcount_union(env, layout_ids, mode, when_recursive, tags);
other_tags: tags, ..
} => { return Some(function);
}
let function = build_rec_union( let function = build_rec_union(
env, env,
layout_ids, layout_ids,
mode, mode,
&WhenRecursive::Loop(*variant), &WhenRecursive::Loop(*variant),
*variant, *variant,
tags,
true,
); );
Some(function) 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)
}
}
}
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(),
)?;
Some(function)
} else {
None
}
}
Struct(layouts) => { Struct(layouts) => {
let function = modify_refcount_struct(env, layout_ids, layouts, mode, when_recursive); let function = modify_refcount_struct(env, layout_ids, layouts, mode, when_recursive);
@ -771,14 +703,14 @@ fn modify_refcount_list<'a, 'ctx, 'env>(
&env.interns, &env.interns,
"increment_list", "increment_list",
"decrement_list", "decrement_list",
&layout, layout,
mode, mode,
); );
let function = match env.module.get_function(fn_name.as_str()) { let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value, Some(function_value) => function_value,
None => { 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); let function_value = build_header(env, basic_type, mode, &fn_name);
modify_refcount_list_help( modify_refcount_list_help(
@ -832,7 +764,7 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
let arg_symbol = Symbol::ARG_1; let arg_symbol = Symbol::ARG_1;
let arg_val = fn_val.get_param_iter().next().unwrap(); 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 parent = fn_val;
let original_wrapper = arg_val.into_struct_value(); let original_wrapper = arg_val.into_struct_value();
@ -908,14 +840,14 @@ fn modify_refcount_str<'a, 'ctx, 'env>(
&env.interns, &env.interns,
"increment_str", "increment_str",
"decrement_str", "decrement_str",
&layout, layout,
mode, mode,
); );
let function = match env.module.get_function(fn_name.as_str()) { let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value, Some(function_value) => function_value,
None => { 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); let function_value = build_header(env, basic_type, mode, &fn_name);
modify_refcount_str_help(env, mode, layout, function_value); 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_symbol = Symbol::ARG_1;
let arg_val = fn_val.get_param_iter().next().unwrap(); 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 parent = fn_val;
@ -1007,14 +939,14 @@ fn modify_refcount_dict<'a, 'ctx, 'env>(
&env.interns, &env.interns,
"increment_dict", "increment_dict",
"decrement_dict", "decrement_dict",
&layout, layout,
mode, mode,
); );
let function = match env.module.get_function(fn_name.as_str()) { let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value, Some(function_value) => function_value,
None => { 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); let function_value = build_header(env, basic_type, mode, &fn_name);
modify_refcount_dict_help( modify_refcount_dict_help(
@ -1070,7 +1002,7 @@ fn modify_refcount_dict_help<'a, 'ctx, 'env>(
let arg_symbol = Symbol::ARG_1; let arg_symbol = Symbol::ARG_1;
let arg_val = fn_val.get_param_iter().next().unwrap(); 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 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. 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); fn_val.set_subprogram(subprogram);
env.dibuilder.finalize(); env.dibuilder.finalize();
@ -1208,10 +1140,8 @@ fn build_rec_union<'a, 'ctx, 'env>(
mode: Mode, mode: Mode,
when_recursive: &WhenRecursive<'a>, when_recursive: &WhenRecursive<'a>,
union_layout: UnionLayout<'a>, union_layout: UnionLayout<'a>,
tags: &'a [&'a [Layout<'a>]],
is_nullable: bool,
) -> FunctionValue<'ctx> { ) -> FunctionValue<'ctx> {
let layout = Layout::Union(UnionLayout::Recursive(tags)); let layout = Layout::Union(union_layout);
let (_, fn_name) = function_name_from_mode( let (_, fn_name) = function_name_from_mode(
layout_ids, 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 block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap(); 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); let function_value = build_header(env, basic_type, mode, &fn_name);
build_rec_union_help( build_rec_union_help(
@ -1237,9 +1167,7 @@ fn build_rec_union<'a, 'ctx, 'env>(
mode, mode,
when_recursive, when_recursive,
union_layout, union_layout,
tags,
function_value, function_value,
is_nullable,
); );
env.builder.position_at_end(block); env.builder.position_at_end(block);
@ -1260,10 +1188,10 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
mode: Mode, mode: Mode,
when_recursive: &WhenRecursive<'a>, when_recursive: &WhenRecursive<'a>,
union_layout: UnionLayout<'a>, union_layout: UnionLayout<'a>,
tags: &'a [&'a [roc_mono::layout::Layout<'a>]],
fn_val: FunctionValue<'ctx>, 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()); debug_assert!(!tags.is_empty());
let context = &env.context; 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(); 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 parent = fn_val;
debug_assert!(arg_val.is_pointer_value()); 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 // to increment/decrement the cons-cell itself
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr); 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, union_layout,
tags, tags,
value_ptr, value_ptr,
current_tag_id,
refcount_ptr, refcount_ptr,
do_recurse_block, do_recurse_block,
DecOrReuse::Dec,
) )
} }
} }
} }
} }
enum DecOrReuse {
Dec,
Reuse,
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>( fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
env: &Env<'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>, union_layout: UnionLayout<'a>,
tags: &[&[Layout<'a>]], tags: &[&[Layout<'a>]],
value_ptr: PointerValue<'ctx>, value_ptr: PointerValue<'ctx>,
current_tag_id: IntValue<'ctx>,
refcount_ptr: PointerToRefcount<'ctx>, refcount_ptr: PointerToRefcount<'ctx>,
match_block: BasicBlock<'ctx>, match_block: BasicBlock<'ctx>,
decrement_or_reuse: DecOrReuse,
) { ) {
let mode = Mode::Dec; let mode = Mode::Dec;
let call_mode = mode_to_call_mode(decrement_fn, mode); 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()); debug_assert!(ptr_as_i64_ptr.is_pointer_value());
// therefore we must cast it to our desired type // therefore we must cast it to our desired type
let union_type = match union_layout { let union_type = basic_type_from_layout(env, &Layout::Union(union_layout));
UnionLayout::NonRecursive(_) => unreachable!(), let recursive_field_ptr = cast_basic_basic(env.builder, ptr_as_i64_ptr, union_type);
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(),
);
deferred_rec.push(recursive_field_ptr); deferred_rec.push(recursive_field_ptr);
} else if field_layout.contains_refcounted() { } 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) // 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 // and store them on the stack, then modify (and potentially free) the current cell, then
// actually inc/dec the fields. // actually inc/dec the fields.
match decrement_or_reuse {
DecOrReuse::Reuse => {}
DecOrReuse::Dec => {
refcount_ptr.modify(call_mode, &Layout::Union(union_layout), env); refcount_ptr.modify(call_mode, &Layout::Union(union_layout), env);
}
}
for (field, field_layout) in deferred_nonrec { for (field, field_layout) in deferred_nonrec {
modify_refcount_layout_help( modify_refcount_layout_help(
@ -1524,25 +1448,182 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
let (_, only_branch) = cases.pop().unwrap(); let (_, only_branch) = cases.pop().unwrap();
env.builder.build_unconditional_branch(only_branch); env.builder.build_unconditional_branch(only_branch);
} else { } else {
// read the tag_id let default_block = env.context.append_basic_block(parent, "switch_default");
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");
// switch on it // switch on it
env.builder 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 // increment/decrement the cons-cell itself
if let DecOrReuse::Dec = decrement_or_reuse {
refcount_ptr.modify(call_mode, &Layout::Union(union_layout), env); refcount_ptr.modify(call_mode, &Layout::Union(union_layout), env);
}
}
// this function returns void // this function returns void
builder.build_return(None); 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>( fn function_name_from_mode<'a>(
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
interns: &Interns, interns: &Interns,
@ -1634,7 +1715,7 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
let arg_symbol = Symbol::ARG_1; let arg_symbol = Symbol::ARG_1;
let arg_val = fn_val.get_param_iter().next().unwrap(); 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 parent = fn_val;

View file

@ -0,0 +1,6 @@
[package]
name = "roc_ident"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2018"

430
compiler/ident/src/lib.rs Normal file
View file

@ -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::<usize>() - 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::<IdentStr>()
}
}
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::<usize>() - 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::<Self>()) {
Ordering::Less => {
// This fits in a small string, but needs its length recorded
let mut answer_bytes: [u8; mem::size_of::<Self>()] = unsafe {
mem::transmute::<Self, [u8; mem::size_of::<Self>()]>(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>()], 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::<usize>() - 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::<Self>()] = unsafe {
mem::transmute::<Self, [u8; mem::size_of::<Self>()]>(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::<Self>())
};
dest_slice.copy_from_slice(slice);
unsafe { mem::transmute::<[u8; mem::size_of::<Self>()], Self>(answer_bytes) }
}
Ordering::Greater => {
// This needs a big string
let elements = unsafe {
let align = mem::align_of::<u8>();
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<String> 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<Ordering> {
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<H>(&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::<usize>();
let copy_length = self.length + capacity_size;
let elements = unsafe {
let align = mem::align_of::<u8>();
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::<u8>();
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);
}

View file

@ -22,7 +22,6 @@ roc_reporting = { path = "../reporting" }
morphic_lib = { path = "../../vendor/morphic_lib" } morphic_lib = { path = "../../vendor/morphic_lib" }
ven_pretty = { path = "../../vendor/pretty" } ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.6.1", features = ["collections"] } bumpalo = { version = "3.6.1", features = ["collections"] }
inlinable_string = "0.1"
parking_lot = { version = "0.11", features = ["deadlock_detection"] } parking_lot = { version = "0.11", features = ["deadlock_detection"] }
crossbeam = "0.7" crossbeam = "0.7"
num_cpus = "1" num_cpus = "1"

View file

@ -3,7 +3,6 @@ use crate::docs::TypeAnnotation::{
Apply, BoundVariable, Function, NoTypeAnn, ObscuredRecord, ObscuredTagUnion, Record, TagUnion, Apply, BoundVariable, Function, NoTypeAnn, ObscuredRecord, ObscuredTagUnion, Record, TagUnion,
}; };
use crate::file::LoadedModule; use crate::file::LoadedModule;
use inlinable_string::InlinableString;
use roc_can::scope::Scope; use roc_can::scope::Scope;
use roc_module::ident::ModuleName; use roc_module::ident::ModuleName;
use roc_module::symbol::IdentIds; use roc_module::symbol::IdentIds;
@ -148,7 +147,7 @@ fn generate_entry_doc<'a>(
match def { match def {
Def::SpaceBefore(sub_def, comments_or_new_lines) => { 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) { for detached_doc in detached_docs_from_comments_and_new_lines(comments_or_new_lines) {
acc.push(DetachedDoc(detached_doc)); acc.push(DetachedDoc(detached_doc));
@ -166,16 +165,14 @@ fn generate_entry_doc<'a>(
(new_acc, Some(comments_or_new_lines)) (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) => { Pattern::Identifier(identifier) => {
// Check if the definition is exposed // Check if the definition is exposed
if ident_ids if ident_ids.get_id(&identifier.into()).is_some() {
.get_id(&InlinableString::from(identifier)) let name = identifier.to_string();
.is_some()
{
let doc_def = DocDef { let doc_def = DocDef {
name: identifier.to_string(), name,
type_annotation: NoTypeAnn, type_annotation: type_to_docs(false, loc_ann.value),
type_vars: Vec::new(), type_vars: Vec::new(),
docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs), 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 { } => match ann_pattern.value {
Pattern::Identifier(identifier) => { Pattern::Identifier(identifier) => {
// Check if the definition is exposed // Check if the definition is exposed
if ident_ids if ident_ids.get_id(&identifier.into()).is_some() {
.get_id(&InlinableString::from(identifier))
.is_some()
{
let doc_def = DocDef { let doc_def = DocDef {
name: identifier.to_string(), name: identifier.to_string(),
type_annotation: type_to_docs(false, ann_type.value), type_annotation: type_to_docs(false, ann_type.value),

View file

@ -1,3 +1,4 @@
use roc_can::annotation::IntroducedVariables;
use roc_can::def::{Declaration, Def}; use roc_can::def::{Declaration, Def};
use roc_can::env::Env; use roc_can::env::Env;
use roc_can::expr::{Expr, Recursive}; use roc_can::expr::{Expr, Recursive};
@ -160,25 +161,22 @@ fn build_effect_always(
(function_var, closure) (function_var, closure)
}; };
use roc_can::annotation::IntroducedVariables;
let mut introduced_variables = IntroducedVariables::default(); let mut introduced_variables = IntroducedVariables::default();
let signature = { let signature = {
// Effect.always : a -> Effect a // Effect.always : a -> Effect a
let var_a = var_store.fresh(); let var_a = var_store.fresh();
introduced_variables.insert_named("a".into(), var_a); introduced_variables.insert_named("a".into(), var_a);
let effect_a = { let effect_a = build_effect_alias(
let actual = build_effect_actual(effect_tag_name, Type::Variable(var_a), var_store);
Type::Alias(
effect_symbol, effect_symbol,
vec![("a".into(), Type::Variable(var_a))], effect_tag_name,
Box::new(actual), "a",
) var_a,
}; Type::Variable(var_a),
var_store,
&mut introduced_variables,
);
let closure_var = var_store.fresh(); let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var); introduced_variables.insert_wildcard(closure_var);
@ -353,8 +351,6 @@ fn build_effect_map(
loc_body: Box::new(Located::at_zero(body)), loc_body: Box::new(Located::at_zero(body)),
}; };
use roc_can::annotation::IntroducedVariables;
let mut introduced_variables = IntroducedVariables::default(); let mut introduced_variables = IntroducedVariables::default();
let signature = { let signature = {
@ -365,26 +361,25 @@ fn build_effect_map(
introduced_variables.insert_named("a".into(), var_a); introduced_variables.insert_named("a".into(), var_a);
introduced_variables.insert_named("b".into(), var_b); introduced_variables.insert_named("b".into(), var_b);
let effect_a = { let effect_a = build_effect_alias(
let actual =
build_effect_actual(effect_tag_name.clone(), Type::Variable(var_a), var_store);
Type::Alias(
effect_symbol, effect_symbol,
vec![("a".into(), Type::Variable(var_a))], effect_tag_name.clone(),
Box::new(actual), "a",
) var_a,
}; Type::Variable(var_a),
var_store,
&mut introduced_variables,
);
let effect_b = { let effect_b = build_effect_alias(
let actual = build_effect_actual(effect_tag_name, Type::Variable(var_b), var_store);
Type::Alias(
effect_symbol, effect_symbol,
vec![("b".into(), Type::Variable(var_b))], effect_tag_name,
Box::new(actual), "b",
) var_b,
}; Type::Variable(var_b),
var_store,
&mut introduced_variables,
);
let closure_var = var_store.fresh(); let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var); introduced_variables.insert_wildcard(closure_var);
@ -526,8 +521,6 @@ fn build_effect_after(
loc_body: Box::new(Located::at_zero(to_effect_call)), loc_body: Box::new(Located::at_zero(to_effect_call)),
}; };
use roc_can::annotation::IntroducedVariables;
let mut introduced_variables = IntroducedVariables::default(); let mut introduced_variables = IntroducedVariables::default();
let signature = { let signature = {
@ -537,26 +530,25 @@ fn build_effect_after(
introduced_variables.insert_named("a".into(), var_a); introduced_variables.insert_named("a".into(), var_a);
introduced_variables.insert_named("b".into(), var_b); introduced_variables.insert_named("b".into(), var_b);
let effect_a = { let effect_a = build_effect_alias(
let actual =
build_effect_actual(effect_tag_name.clone(), Type::Variable(var_a), var_store);
Type::Alias(
effect_symbol, effect_symbol,
vec![("a".into(), Type::Variable(var_a))], effect_tag_name.clone(),
Box::new(actual), "a",
) var_a,
}; Type::Variable(var_a),
var_store,
&mut introduced_variables,
);
let effect_b = { let effect_b = build_effect_alias(
let actual = build_effect_actual(effect_tag_name, Type::Variable(var_b), var_store);
Type::Alias(
effect_symbol, effect_symbol,
vec![("b".into(), Type::Variable(var_b))], effect_tag_name,
Box::new(actual), "b",
) var_b,
}; Type::Variable(var_b),
var_store,
&mut introduced_variables,
);
let closure_var = var_store.fresh(); let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var); 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( pub fn build_effect_actual(
effect_tag_name: TagName, effect_tag_name: TagName,
a_type: Type, a_type: Type,

View file

@ -225,7 +225,7 @@ impl<'a> Dependencies<'a> {
if let Some(to_notify) = self.notifies.get(&key) { if let Some(to_notify) = self.notifies.get(&key) {
for notify_key in to_notify { for notify_key in to_notify {
let mut is_empty = false; let mut is_empty = false;
if let Some(waiting_for_pairs) = self.waiting_for.get_mut(&notify_key) { if let Some(waiting_for_pairs) = self.waiting_for.get_mut(notify_key) {
waiting_for_pairs.remove(&key); waiting_for_pairs.remove(&key);
is_empty = waiting_for_pairs.is_empty(); is_empty = waiting_for_pairs.is_empty();
} }
@ -469,7 +469,7 @@ fn start_phase<'a>(
for dep_id in deps_by_name.values() { for dep_id in deps_by_name.values() {
// We already verified that these are all present, // We already verified that these are all present,
// so unwrapping should always succeed here. // 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()); dep_idents.insert(*dep_id, idents.clone());
} }
@ -524,6 +524,7 @@ fn start_phase<'a>(
var_store, var_store,
imported_modules, imported_modules,
declarations, declarations,
dep_idents,
.. ..
} = constrained; } = constrained;
@ -535,7 +536,8 @@ fn start_phase<'a>(
var_store, var_store,
imported_modules, imported_modules,
&mut state.exposed_types, &mut state.exposed_types,
&state.stdlib, state.stdlib,
dep_idents,
declarations, declarations,
) )
} }
@ -621,6 +623,7 @@ pub struct LoadedModule {
pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>, pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>,
pub declarations_by_id: MutMap<ModuleId, Vec<Declaration>>, pub declarations_by_id: MutMap<ModuleId, Vec<Declaration>>,
pub exposed_to_host: MutMap<Symbol, Variable>, pub exposed_to_host: MutMap<Symbol, Variable>,
pub dep_idents: MutMap<ModuleId, IdentIds>,
pub exposed_aliases: MutMap<Symbol, Alias>, pub exposed_aliases: MutMap<Symbol, Alias>,
pub exposed_values: Vec<Symbol>, pub exposed_values: Vec<Symbol>,
pub header_sources: MutMap<ModuleId, (PathBuf, Box<str>)>, pub header_sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
@ -676,6 +679,7 @@ struct ConstrainedModule {
constraint: Constraint, constraint: Constraint,
ident_ids: IdentIds, ident_ids: IdentIds,
var_store: VarStore, var_store: VarStore,
dep_idents: MutMap<ModuleId, IdentIds>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
} }
@ -759,6 +763,7 @@ enum Msg<'a> {
solved_module: SolvedModule, solved_module: SolvedModule,
solved_subs: Solved<Subs>, solved_subs: Solved<Subs>,
decls: Vec<Declaration>, decls: Vec<Declaration>,
dep_idents: MutMap<ModuleId, IdentIds>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
unused_imports: MutMap<ModuleId, Region>, unused_imports: MutMap<ModuleId, Region>,
}, },
@ -767,6 +772,7 @@ enum Msg<'a> {
exposed_vars_by_symbol: MutMap<Symbol, Variable>, exposed_vars_by_symbol: MutMap<Symbol, Variable>,
exposed_aliases_by_symbol: MutMap<Symbol, Alias>, exposed_aliases_by_symbol: MutMap<Symbol, Alias>,
exposed_values: Vec<Symbol>, exposed_values: Vec<Symbol>,
dep_idents: MutMap<ModuleId, IdentIds>,
documentation: MutMap<ModuleId, ModuleDocumentation>, documentation: MutMap<ModuleId, ModuleDocumentation>,
}, },
FoundSpecializations { FoundSpecializations {
@ -985,6 +991,7 @@ enum BuildTask<'a> {
constraint: Constraint, constraint: Constraint,
var_store: VarStore, var_store: VarStore,
declarations: Vec<Declaration>, declarations: Vec<Declaration>,
dep_idents: MutMap<ModuleId, IdentIds>,
unused_imports: MutMap<ModuleId, Region>, unused_imports: MutMap<ModuleId, Region>,
}, },
BuildPendingSpecializations { BuildPendingSpecializations {
@ -1516,6 +1523,7 @@ where
exposed_vars_by_symbol, exposed_vars_by_symbol,
exposed_aliases_by_symbol, exposed_aliases_by_symbol,
exposed_values, exposed_values,
dep_idents,
documentation, documentation,
} => { } => {
// We're done! There should be no more messages pending. // We're done! There should be no more messages pending.
@ -1534,6 +1542,7 @@ where
exposed_values, exposed_values,
exposed_aliases_by_symbol, exposed_aliases_by_symbol,
exposed_vars_by_symbol, exposed_vars_by_symbol,
dep_idents,
documentation, documentation,
))); )));
} }
@ -1635,7 +1644,7 @@ fn start_tasks<'a>(
) -> Result<(), LoadingProblem<'a>> { ) -> Result<(), LoadingProblem<'a>> {
for (module_id, phase) in work { for (module_id, phase) in work {
for task in start_phase(module_id, phase, arena, state) { 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); 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); 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) Ok(state)
} }
@ -1796,7 +1805,7 @@ fn update<'a>(
let work = state.dependencies.notify(module_id, Phase::Parse); 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) Ok(state)
} }
@ -1831,7 +1840,7 @@ fn update<'a>(
.dependencies .dependencies
.notify(module_id, Phase::CanonicalizeAndConstrain); .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) Ok(state)
} }
@ -1882,7 +1891,7 @@ fn update<'a>(
.notify(module_id, Phase::CanonicalizeAndConstrain), .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) Ok(state)
} }
@ -1892,6 +1901,7 @@ fn update<'a>(
solved_module, solved_module,
solved_subs, solved_subs,
decls, decls,
dep_idents,
mut module_timing, mut module_timing,
mut unused_imports, mut unused_imports,
} => { } => {
@ -1949,6 +1959,7 @@ fn update<'a>(
exposed_vars_by_symbol: solved_module.exposed_vars_by_symbol, exposed_vars_by_symbol: solved_module.exposed_vars_by_symbol,
exposed_values: solved_module.exposed_symbols, exposed_values: solved_module.exposed_symbols,
exposed_aliases_by_symbol: solved_module.aliases, exposed_aliases_by_symbol: solved_module.aliases,
dep_idents,
documentation, documentation,
}) })
.map_err(|_| LoadingProblem::MsgChannelDied)?; .map_err(|_| LoadingProblem::MsgChannelDied)?;
@ -1986,7 +1997,7 @@ fn update<'a>(
state.constrained_ident_ids.insert(module_id, ident_ids); 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) Ok(state)
@ -2041,13 +2052,13 @@ fn update<'a>(
.dependencies .dependencies
.notify(module_id, Phase::FindSpecializations); .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) Ok(state)
} }
MadeSpecializations { MadeSpecializations {
module_id, module_id,
ident_ids, mut ident_ids,
subs, subs,
procedures, procedures,
external_specializations_requested, external_specializations_requested,
@ -2070,6 +2081,15 @@ fn update<'a>(
&& state.dependencies.solved_all() && state.dependencies.solved_all()
&& state.goal_phase == Phase::MakeSpecializations && 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 // display the mono IR of the module, for debug purposes
if roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS { if roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS {
let procs_string = state let procs_string = state
@ -2083,8 +2103,6 @@ fn update<'a>(
println!("{}", result); 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 // This is not safe with the new non-recursive RC updates that we do for tag unions
// //
// Proc::optimize_refcount_operations( // Proc::optimize_refcount_operations(
@ -2136,7 +2154,7 @@ fn update<'a>(
existing.extend(requested); existing.extend(requested);
} }
start_tasks(arena, &mut state, work, &injector, worker_listeners)?; start_tasks(arena, &mut state, work, injector, worker_listeners)?;
} }
Ok(state) Ok(state)
@ -2276,6 +2294,7 @@ fn finish(
exposed_values: Vec<Symbol>, exposed_values: Vec<Symbol>,
exposed_aliases_by_symbol: MutMap<Symbol, Alias>, exposed_aliases_by_symbol: MutMap<Symbol, Alias>,
exposed_vars_by_symbol: MutMap<Symbol, Variable>, exposed_vars_by_symbol: MutMap<Symbol, Variable>,
dep_idents: MutMap<ModuleId, IdentIds>,
documentation: MutMap<ModuleId, ModuleDocumentation>, documentation: MutMap<ModuleId, ModuleDocumentation>,
) -> LoadedModule { ) -> LoadedModule {
let module_ids = Arc::try_unwrap(state.arc_modules) let module_ids = Arc::try_unwrap(state.arc_modules)
@ -2309,6 +2328,7 @@ fn finish(
can_problems: state.module_cache.can_problems, can_problems: state.module_cache.can_problems,
type_problems: state.module_cache.type_problems, type_problems: state.module_cache.type_problems,
declarations_by_id: state.declarations_by_id, declarations_by_id: state.declarations_by_id,
dep_idents,
exposed_aliases: exposed_aliases_by_symbol, exposed_aliases: exposed_aliases_by_symbol,
exposed_values, exposed_values,
exposed_to_host: exposed_vars_by_symbol.into_iter().collect(), 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 parse_start = SystemTime::now();
let bytes = arena.alloc(bytes_vec); let bytes = arena.alloc(bytes_vec);
let parse_state = parser::State::new(bytes); 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(); let parse_header_duration = parse_start.elapsed().unwrap();
// Insert the first entries for this module's timings // Insert the first entries for this module's timings
@ -2511,7 +2531,7 @@ fn parse_header<'a>(
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> { ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let parse_start = SystemTime::now(); let parse_start = SystemTime::now();
let parse_state = parser::State::new(src_bytes); 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(); let parse_header_duration = parse_start.elapsed().unwrap();
// Insert the first entries for this module's timings // 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( Ok((ast::Module::Platform { header }, _parse_state)) => Ok(fabricate_effects_module(
arena, arena,
&"", "",
module_ids, module_ids,
ident_ids_by_module, ident_ids_by_module,
header, header,
@ -2817,10 +2837,8 @@ fn send_header<'a>(
let mut ident_ids_by_module = (*ident_ids_by_module).lock(); let mut ident_ids_by_module = (*ident_ids_by_module).lock();
let name = match opt_shorthand { let name = match opt_shorthand {
Some(shorthand) => { Some(shorthand) => PQModuleName::Qualified(shorthand, declared_name),
PQModuleName::Qualified(&shorthand, declared_name.as_inline_str().clone()) None => PQModuleName::Unqualified(declared_name),
}
None => PQModuleName::Unqualified(declared_name.as_inline_str().clone()),
}; };
home = module_ids.get_or_insert(&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 { let pq_module_name = match qualified_module_name.opt_package {
None => match opt_shorthand { None => match opt_shorthand {
Some(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) => { Some(package) => PQModuleName::Qualified(package, cloned_module_name),
PQModuleName::Qualified(package, cloned_module_name.clone().into())
}
}; };
let module_id = module_ids.get_or_insert(&pq_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); .or_insert_with(IdentIds::default);
for ident in exposed_idents { 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); let symbol = Symbol::new(module_id, ident_id);
// Since this value is exposed, add it to our module's default scope. // 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) { if cfg!(debug_assertions) {
home.register_debug_idents(&ident_ids); home.register_debug_idents(ident_ids);
} }
ident_ids.clone() 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(); let mut package_entries = MutMap::default();
while let Some(parse_entry) = parse_entries.pop() { while let Some(parse_entry) = parse_entries.pop() {
@ -2991,8 +3007,6 @@ fn send_header_two<'a>(
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
) -> (ModuleId, Msg<'a>) { ) -> (ModuleId, Msg<'a>) {
use inlinable_string::InlinableString;
let PlatformHeaderInfo { let PlatformHeaderInfo {
filename, filename,
shorthand, shorthand,
@ -3005,7 +3019,7 @@ fn send_header_two<'a>(
imports, imports,
} = info; } = info;
let declared_name: InlinableString = "".into(); let declared_name: ModuleName = "".into();
let mut imported: Vec<(QualifiedModuleName, Vec<Ident>, Region)> = let mut imported: Vec<(QualifiedModuleName, Vec<Ident>, Region)> =
Vec::with_capacity(imports.len()); Vec::with_capacity(imports.len());
@ -3046,7 +3060,7 @@ fn send_header_two<'a>(
let mut module_ids = (*module_ids).lock(); let mut module_ids = (*module_ids).lock();
let mut ident_ids_by_module = (*ident_ids_by_module).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); home = module_ids.get_or_insert(&name);
// Ensure this module has an entry in the exposed_ident_ids map. // 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() { for (qualified_module_name, exposed_idents, region) in imported.into_iter() {
let cloned_module_name = qualified_module_name.module.clone(); let cloned_module_name = qualified_module_name.module.clone();
let pq_module_name = match qualified_module_name.opt_package { let pq_module_name = match qualified_module_name.opt_package {
None => PQModuleName::Qualified(shorthand, qualified_module_name.module.into()), None => PQModuleName::Qualified(shorthand, qualified_module_name.module),
Some(package) => { Some(package) => PQModuleName::Qualified(package, cloned_module_name.clone()),
PQModuleName::Qualified(package, cloned_module_name.clone().into())
}
}; };
let module_id = module_ids.get_or_insert(&pq_module_name); 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); .or_insert_with(IdentIds::default);
for ident in exposed_idents { 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); let symbol = Symbol::new(module_id, ident_id);
// Since this value is exposed, add it to our module's default scope. // 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) { for (loc_ident, _) in unpack_exposes_entries(arena, requires) {
let ident: Ident = loc_ident.value.into(); 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); let symbol = Symbol::new(app_module_id, ident_id);
// Since this value is exposed, add it to our module's default scope. // 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) { if cfg!(debug_assertions) {
home.register_debug_idents(&ident_ids); home.register_debug_idents(ident_ids);
} }
ident_ids.clone() 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(); let mut package_entries = MutMap::default();
while let Some(parse_entry) = parse_entries.pop() { while let Some(parse_entry) = parse_entries.pop() {
@ -3165,7 +3177,7 @@ fn send_header_two<'a>(
let module_name = ModuleNameEnum::PkgConfig; let module_name = ModuleNameEnum::PkgConfig;
let main_for_host = { 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); let ident_id = ident_ids.get_or_insert(&ident_str);
Symbol::new(home, ident_id) Symbol::new(home, ident_id)
@ -3227,6 +3239,7 @@ impl<'a> BuildTask<'a> {
imported_modules: MutMap<ModuleId, Region>, imported_modules: MutMap<ModuleId, Region>,
exposed_types: &mut SubsByModule, exposed_types: &mut SubsByModule,
stdlib: &StdLib, stdlib: &StdLib,
dep_idents: MutMap<ModuleId, IdentIds>,
declarations: Vec<Declaration>, declarations: Vec<Declaration>,
) -> Self { ) -> Self {
let home = module.module_id; let home = module.module_id;
@ -3254,6 +3267,7 @@ impl<'a> BuildTask<'a> {
constraint, constraint,
var_store, var_store,
declarations, declarations,
dep_idents,
module_timing, module_timing,
unused_imports, unused_imports,
} }
@ -3269,6 +3283,7 @@ fn run_solve<'a>(
constraint: Constraint, constraint: Constraint,
mut var_store: VarStore, mut var_store: VarStore,
decls: Vec<Declaration>, decls: Vec<Declaration>,
dep_idents: MutMap<ModuleId, IdentIds>,
unused_imports: MutMap<ModuleId, Region>, unused_imports: MutMap<ModuleId, Region>,
) -> Msg<'a> { ) -> Msg<'a> {
// We have more constraining work to do now, so we'll add it to our timings. // 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, solved_subs,
ident_ids, ident_ids,
decls, decls,
dep_idents,
solved_module, solved_module,
module_timing, module_timing,
unused_imports, unused_imports,
@ -3383,7 +3399,7 @@ fn fabricate_effects_module<'a>(
let module_id: ModuleId; 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 name = effects.effect_type_name;
let declared_name: ModuleName = name.into(); let declared_name: ModuleName = name.into();
@ -3402,7 +3418,10 @@ fn fabricate_effects_module<'a>(
for exposed in header.exposes { for exposed in header.exposes {
if let ExposesEntry::Exposed(module_name) = exposed.value { 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 module_ids = (*module_ids).lock();
let mut ident_ids_by_module = (*ident_ids_by_module).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); module_id = module_ids.get_or_insert(&name);
// Ensure this module has an entry in the exposed_ident_ids map. // 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) { if cfg!(debug_assertions) {
module_id.register_debug_idents(&ident_ids); module_id.register_debug_idents(ident_ids);
} }
ident_ids.clone() ident_ids.clone()
@ -3471,7 +3490,8 @@ fn fabricate_effects_module<'a>(
let module_ids = { (*module_ids).lock().clone() }.into_module_ids(); 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 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 let effect_symbol = scope
.introduce( .introduce(
@ -3604,6 +3624,7 @@ fn fabricate_effects_module<'a>(
var_store, var_store,
constraint, constraint,
ident_ids: module_output.ident_ids, ident_ids: module_output.ident_ids,
dep_idents,
module_timing, module_timing,
}; };
@ -3678,12 +3699,12 @@ where
let mut var_store = VarStore::default(); let mut var_store = VarStore::default();
let canonicalized = canonicalize_module_defs( let canonicalized = canonicalize_module_defs(
&arena, arena,
parsed_defs, parsed_defs,
module_id, module_id,
module_ids, module_ids,
exposed_ident_ids, exposed_ident_ids,
dep_idents, &dep_idents,
aliases, aliases,
exposed_imports, exposed_imports,
&exposed_symbols, &exposed_symbols,
@ -3705,7 +3726,7 @@ where
module_output.scope, module_output.scope,
name.as_str().into(), name.as_str().into(),
&module_output.ident_ids, &module_output.ident_ids,
&parsed_defs, parsed_defs,
)), )),
}; };
@ -3731,6 +3752,7 @@ where
var_store, var_store,
constraint, constraint,
ident_ids: module_output.ident_ids, ident_ids: module_output.ident_ids,
dep_idents,
module_timing, module_timing,
}; };
@ -3754,7 +3776,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, Loadi
let parse_start = SystemTime::now(); let parse_start = SystemTime::now();
let source = header.parse_state.bytes; let source = header.parse_state.bytes;
let parse_state = header.parse_state; 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, Ok((_, success, _state)) => success,
Err((_, fail, _)) => { Err((_, fail, _)) => {
return Err(LoadingProblem::ParsingFailed(fail.into_parse_problem( return Err(LoadingProblem::ParsingFailed(fail.into_parse_problem(
@ -4038,7 +4060,7 @@ fn add_def_to_module<'a>(
pattern_vars.push(*var); pattern_vars.push(*var);
} }
let layout = match layout_cache.from_var( let layout = match layout_cache.raw_from_var(
mono_env.arena, mono_env.arena,
annotation, annotation,
mono_env.subs, mono_env.subs,
@ -4063,7 +4085,7 @@ fn add_def_to_module<'a>(
procs.insert_exposed( procs.insert_exposed(
symbol, symbol,
ProcLayout::from_layout(mono_env.arena, layout), ProcLayout::from_raw(mono_env.arena, layout),
mono_env.arena, mono_env.arena,
mono_env.subs, mono_env.subs,
def.annotation, def.annotation,
@ -4203,6 +4225,7 @@ where
var_store, var_store,
ident_ids, ident_ids,
declarations, declarations,
dep_idents,
unused_imports, unused_imports,
} => Ok(run_solve( } => Ok(run_solve(
module, module,
@ -4212,6 +4235,7 @@ where
constraint, constraint,
var_store, var_store,
declarations, declarations,
dep_idents,
unused_imports, unused_imports,
)), )),
BuildPendingSpecializations { BuildPendingSpecializations {

View file

@ -6,7 +6,6 @@ extern crate pretty_assertions;
extern crate maplit; extern crate maplit;
extern crate bumpalo; extern crate bumpalo;
extern crate inlinable_string;
extern crate roc_collections; extern crate roc_collections;
extern crate roc_load; extern crate roc_load;
extern crate roc_module; extern crate roc_module;
@ -17,7 +16,6 @@ mod helpers;
mod test_load { mod test_load {
use crate::helpers::fixtures_dir; use crate::helpers::fixtures_dir;
use bumpalo::Bump; use bumpalo::Bump;
use inlinable_string::InlinableString;
use roc_can::builtins::builtin_defs_map; use roc_can::builtins::builtin_defs_map;
use roc_can::def::Declaration::*; use roc_can::def::Declaration::*;
use roc_can::def::Def; use roc_can::def::Def;
@ -169,8 +167,8 @@ mod test_load {
.expect("Test ModuleID not found in module_ids"); .expect("Test ModuleID not found in module_ids");
// App module names are hardcoded and not based on anything user-specified // App module names are hardcoded and not based on anything user-specified
if expected_name != ModuleName::APP { if expected_name.as_str() != ModuleName::APP {
assert_eq!(expected_name, &InlinableString::from(module_name)); assert_eq!(&expected_name.as_str(), &module_name);
} }
loaded_module loaded_module
@ -184,12 +182,11 @@ mod test_load {
expected_types: &mut HashMap<&str, &str>, expected_types: &mut HashMap<&str, &str>,
) { ) {
for (symbol, expr_var) in &def.pattern_vars { for (symbol, expr_var) in &def.pattern_vars {
let content = subs.get(*expr_var).content;
name_all_type_vars(*expr_var, subs); name_all_type_vars(*expr_var, subs);
let actual_str = content_to_string(content, subs, home, &interns); let content = subs.get_content_without_compacting(*expr_var);
let fully_qualified = symbol.fully_qualified(&interns, home).to_string(); 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 let expected_type = expected_types
.remove(fully_qualified.as_str()) .remove(fully_qualified.as_str())
.unwrap_or_else(|| { .unwrap_or_else(|| {
@ -341,7 +338,7 @@ mod test_load {
.get_name(loaded_module.module_id) .get_name(loaded_module.module_id)
.expect("Test ModuleID not found in module_ids"); .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); assert_eq!(def_count, 10);
} }

View file

@ -7,10 +7,11 @@ license = "UPL-1.0"
[dependencies] [dependencies]
roc_region = { path = "../region" } roc_region = { path = "../region" }
roc_ident = { path = "../ident" }
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
bumpalo = { version = "3.6.1", features = ["collections"] } bumpalo = { version = "3.6.1", features = ["collections"] }
inlinable_string = "0.1"
lazy_static = "1.4" lazy_static = "1.4"
static_assertions = "1.1.0"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1" pretty_assertions = "0.5.1"

View file

@ -1,13 +1,13 @@
use crate::symbol::{Interns, ModuleId, Symbol}; use crate::symbol::{Interns, ModuleId, Symbol};
use inlinable_string::InlinableString; pub use roc_ident::IdentStr;
use std::fmt; use std::fmt;
/// This could be uppercase or lowercase, qualified or unqualified. /// This could be uppercase or lowercase, qualified or unqualified.
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Ident(InlinableString); pub struct Ident(IdentStr);
impl Ident { impl Ident {
pub fn as_inline_str(&self) -> &InlinableString { pub fn as_inline_str(&self) -> &IdentStr {
&self.0 &self.0
} }
} }
@ -18,24 +18,32 @@ pub struct QualifiedModuleName<'a> {
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] #[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 /// An uncapitalized identifier, such as a field name or local variable
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[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 /// A capitalized identifier, such as a tag name or module name
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Uppercase(InlinableString); pub struct Uppercase(IdentStr);
/// A string representing a foreign (linked-in) symbol /// A string representing a foreign (linked-in) symbol
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
pub struct ForeignSymbol(InlinableString); pub struct ForeignSymbol(IdentStr);
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum TagName { pub enum TagName {
/// Global tags have no module, but tend to be short strings (since they're /// 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. /// This is allows canonicalization to happen in parallel without locks.
/// If global tags had a Symbol representation, then each module would have to /// If global tags had a Symbol representation, then each module would have to
@ -51,12 +59,18 @@ pub enum TagName {
Closure(Symbol), Closure(Symbol),
} }
static_assertions::assert_eq_size!([u8; 24], TagName);
impl 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 { match self {
TagName::Global(uppercase) => uppercase.as_inline_str().clone(), TagName::Global(uppercase) => uppercase.as_ident_str().clone(),
TagName::Private(symbol) => symbol.fully_qualified(interns, home), TagName::Private(symbol) => {
TagName::Closure(symbol) => symbol.fully_qualified(interns, home), 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 const RESULT: &'static str = "Result";
pub fn as_str(&self) -> &str { 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 &self.0
} }
@ -99,6 +113,12 @@ impl<'a> From<&'a str> for ModuleName {
} }
} }
impl<'a> From<IdentStr> for ModuleName {
fn from(string: IdentStr) -> Self {
Self(string.as_str().into())
}
}
impl From<Box<str>> for ModuleName { impl From<Box<str>> for ModuleName {
fn from(string: Box<str>) -> Self { fn from(string: Box<str>) -> Self {
Self((string.as_ref()).into()) Self((string.as_ref()).into())
@ -111,36 +131,24 @@ impl From<String> for ModuleName {
} }
} }
impl From<InlinableString> for ModuleName {
fn from(string: InlinableString) -> Self {
Self(string)
}
}
impl From<ModuleName> 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<ModuleName> for Box<str> { impl From<ModuleName> for Box<str> {
fn from(name: ModuleName) -> Self { fn from(name: ModuleName) -> Self {
name.0.to_string().into() 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 { impl ForeignSymbol {
pub fn as_str(&self) -> &str { 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 &self.0
} }
} }
@ -159,10 +167,10 @@ impl<'a> From<String> for ForeignSymbol {
impl Uppercase { impl Uppercase {
pub fn as_str(&self) -> &str { 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 &self.0
} }
} }
@ -181,7 +189,7 @@ impl<'a> From<String> for Uppercase {
impl Lowercase { impl Lowercase {
pub fn as_str(&self) -> &str { pub fn as_str(&self) -> &str {
&*self.0 self.0.as_str()
} }
} }
@ -197,16 +205,10 @@ impl<'a> From<String> for Lowercase {
} }
} }
impl From<Lowercase> for InlinableString {
fn from(lowercase: Lowercase) -> Self {
lowercase.0
}
}
impl AsRef<str> for Ident { impl AsRef<str> for Ident {
#[inline(always)] #[inline(always)]
fn as_ref(&self) -> &str { fn as_ref(&self) -> &str {
self.0.as_ref() self.0.as_str()
} }
} }
@ -228,19 +230,19 @@ impl From<String> for Ident {
} }
} }
impl From<InlinableString> for Ident { impl From<IdentStr> for Ident {
fn from(string: InlinableString) -> Self { fn from(string: IdentStr) -> Self {
Self(string) Self(string)
} }
} }
impl From<Ident> for InlinableString { impl From<Ident> for IdentStr {
fn from(ident: Ident) -> Self { fn from(ident: Ident) -> Self {
ident.0 ident.0
} }
} }
impl<'a> From<&'a Ident> for &'a InlinableString { impl<'a> From<&'a Ident> for &'a IdentStr {
fn from(ident: &'a Ident) -> Self { fn from(ident: &'a Ident) -> Self {
&ident.0 &ident.0
} }
@ -252,6 +254,12 @@ impl From<Ident> for Box<str> {
} }
} }
impl fmt::Display for Ident {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
/// Rather than displaying as this: /// Rather than displaying as this:
/// ///
/// Lowercase("foo") /// Lowercase("foo")

View file

@ -7,13 +7,14 @@ pub enum LowLevel {
StrJoinWith, StrJoinWith,
StrIsEmpty, StrIsEmpty,
StrStartsWith, StrStartsWith,
StrStartsWithCodePoint, StrStartsWithCodePt,
StrEndsWith, StrEndsWith,
StrSplit, StrSplit,
StrCountGraphemes, StrCountGraphemes,
StrFromInt, StrFromInt,
StrFromUtf8, StrFromUtf8,
StrToBytes, StrFromUtf8Range,
StrToUtf8,
StrFromFloat, StrFromFloat,
ListLen, ListLen,
ListGetUnsafe, ListGetUnsafe,
@ -86,6 +87,8 @@ pub enum LowLevel {
NumAtan, NumAtan,
NumAcos, NumAcos,
NumAsin, NumAsin,
NumBytesToU16,
NumBytesToU32,
NumBitwiseAnd, NumBitwiseAnd,
NumBitwiseXor, NumBitwiseXor,
NumBitwiseOr, NumBitwiseOr,
@ -109,91 +112,21 @@ impl LowLevel {
use LowLevel::*; use LowLevel::*;
match self { match self {
StrConcat StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt
| StrJoinWith | StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8
| StrIsEmpty | StrFromUtf8Range | StrToUtf8 | StrFromFloat | ListLen | ListGetUnsafe | ListSet
| StrStartsWith | ListDrop | ListSingle | ListRepeat | ListReverse | ListConcat | ListContains
| StrStartsWithCodePoint | ListAppend | ListPrepend | ListJoin | ListRange | ListSwap | DictSize | DictEmpty
| StrEndsWith | DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues
| StrSplit | DictUnion | DictIntersection | DictDifference | SetFromList | NumAdd | NumAddWrap
| StrCountGraphemes | NumAddChecked | NumSub | NumSubWrap | NumSubChecked | NumMul | NumMulWrap
| StrFromInt | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare | NumDivUnchecked
| StrFromUtf8 | NumRemUnchecked | NumIsMultipleOf | NumAbs | NumNeg | NumSin | NumCos
| StrToBytes | NumSqrtUnchecked | NumLogUnchecked | NumRound | NumToFloat | NumPow | NumCeiling
| StrFromFloat | NumPowInt | NumFloor | NumIsFinite | NumAtan | NumAcos | NumAsin | NumBitwiseAnd
| ListLen | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumBytesToU16
| ListGetUnsafe | NumBytesToU32 | NumShiftRightZfBy | NumIntCast | Eq | NotEq | And | Or | Not
| ListSet | Hash | ExpectTrue => false,
| 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,
ListMap | ListMap2 | ListMap3 | ListMapWithIndex | ListKeepIf | ListWalk ListMap | ListMap2 | ListMap3 | ListMapWithIndex | ListKeepIf | ListWalk
| ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith

View file

@ -1,6 +1,6 @@
use crate::ident::Ident; use crate::ident::{Ident, ModuleName};
use inlinable_string::InlinableString;
use roc_collections::all::{default_hasher, MutMap, SendMap}; use roc_collections::all::{default_hasher, MutMap, SendMap};
use roc_ident::IdentStr;
use roc_region::all::Region; use roc_region::all::Region;
use std::collections::HashMap; use std::collections::HashMap;
use std::{fmt, u32}; use std::{fmt, u32};
@ -52,7 +52,7 @@ impl Symbol {
self.module_id().is_builtin() 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 interns
.module_ids .module_ids
.get_name(self.module_id()) .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 let ident_ids = interns
.all_ident_ids .all_ident_ids
.get(&self.module_id()) .get(&self.module_id())
@ -77,30 +81,33 @@ impl Symbol {
) )
}); });
ident_ids.get_name(self.ident_id()).unwrap_or_else(|| { ident_ids
.get_name(self.ident_id())
.unwrap_or_else(|| {
panic!( panic!(
"ident_string's IdentIds did not contain an entry for {} in module {:?}", "ident_string's IdentIds did not contain an entry for {} in module {:?}",
self.ident_id().0, self.ident_id().0,
self.module_id() self.module_id()
) )
}) })
.into()
} }
pub fn as_u64(self) -> u64 { pub fn as_u64(self) -> u64 {
self.0 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(); let module_id = self.module_id();
if module_id == home { if module_id == home {
self.ident_string(interns).clone() self.ident_str(interns).clone().into()
} else { } else {
// TODO do this without format! to avoid allocation for short strings // TODO do this without format! to avoid allocation for short strings
format!( format!(
"{}.{}", "{}.{}",
self.module_string(interns), self.module_string(interns).as_str(),
self.ident_string(interns) self.ident_str(interns)
) )
.into() .into()
} }
@ -209,11 +216,11 @@ pub struct Interns {
} }
impl 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) 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(|| { self.module_ids.get_name(module_id).unwrap_or_else(|| {
panic!( panic!(
"Unable to find interns entry for module_id {:?} in Interns {:?}", "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) { match self.all_ident_ids.get(&module_id) {
Some(ident_ids) => match ident_ids.get_id(&ident) { Some(ident_ids) => match ident_ids.get_id(&ident) {
Some(ident_id) => Symbol::new(module_id, *ident_id), 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 // 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 interns
.module_ids .module_ids
.get_name(self) .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))] #[cfg(not(debug_assertions))]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f) self.0.fmt(f)
@ -338,7 +347,7 @@ pub enum PackageQualified<'a, T> {
} }
/// Package-qualified module name /// Package-qualified module name
pub type PQModuleName<'a> = PackageQualified<'a, InlinableString>; pub type PQModuleName<'a> = PackageQualified<'a, ModuleName>;
impl<'a, T> PackageQualified<'a, T> { impl<'a, T> PackageQualified<'a, T> {
pub fn as_inner(&self) -> &T { pub fn as_inner(&self) -> &T {
@ -368,7 +377,7 @@ impl<'a> PackageModuleIds<'a> {
self.by_name.insert(module_name.clone(), module_id); self.by_name.insert(module_name.clone(), module_id);
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
Self::insert_debug_name(module_id, &module_name); Self::insert_debug_name(module_id, module_name);
} }
module_id module_id
@ -377,13 +386,13 @@ impl<'a> PackageModuleIds<'a> {
} }
pub fn into_module_ids(self) -> ModuleIds { pub fn into_module_ids(self) -> ModuleIds {
let by_name: MutMap<InlinableString, ModuleId> = self let by_name: MutMap<ModuleName, ModuleId> = self
.by_name .by_name
.into_iter() .into_iter()
.map(|(pqname, module_id)| (pqname.as_inner().clone(), module_id)) .map(|(pqname, module_id)| (pqname.as_inner().clone(), module_id))
.collect(); .collect();
let by_id: Vec<InlinableString> = self let by_id: Vec<ModuleName> = self
.by_id .by_id
.into_iter() .into_iter()
.map(|pqname| pqname.as_inner().clone()) .map(|pqname| pqname.as_inner().clone())
@ -399,9 +408,9 @@ impl<'a> PackageModuleIds<'a> {
names names
.entry(module_id.0) .entry(module_id.0)
.or_insert_with(|| match module_name { .or_insert_with(|| match module_name {
PQModuleName::Unqualified(module) => module.to_string().into(), PQModuleName::Unqualified(module) => module.as_str().into(),
PQModuleName::Qualified(package, module) => { PQModuleName::Qualified(package, module) => {
let name = format!("{}.{}", package, module).into(); let name = format!("{}.{}", package, module.as_str()).into();
name name
} }
}); });
@ -431,13 +440,13 @@ impl<'a> PackageModuleIds<'a> {
/// Since these are interned strings, this shouldn't result in many total allocations in practice. /// Since these are interned strings, this shouldn't result in many total allocations in practice.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ModuleIds { pub struct ModuleIds {
by_name: MutMap<InlinableString, ModuleId>, by_name: MutMap<ModuleName, ModuleId>,
/// Each ModuleId is an index into this Vec /// Each ModuleId is an index into this Vec
by_id: Vec<InlinableString>, by_id: Vec<ModuleName>,
} }
impl ModuleIds { 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) { match self.by_name.get(module_name) {
Some(id) => *id, Some(id) => *id,
None => { None => {
@ -449,7 +458,7 @@ impl ModuleIds {
self.by_name.insert(module_name.clone(), module_id); self.by_name.insert(module_name.clone(), module_id);
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
Self::insert_debug_name(module_id, &module_name); Self::insert_debug_name(module_id, module_name);
} }
module_id module_id
@ -458,29 +467,29 @@ impl ModuleIds {
} }
#[cfg(debug_assertions)] #[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."); 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! // TODO make sure modules are never added more than once!
names names
.entry(module_id.0) .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))] #[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! // 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) 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) self.by_id.get(id.0 as usize)
} }
pub fn available_modules(&self) -> impl Iterator<Item = &InlinableString> { pub fn available_modules(&self) -> impl Iterator<Item = &ModuleName> {
self.by_id.iter() 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. /// Since these are interned strings, this shouldn't result in many total allocations in practice.
#[derive(Clone, Debug, Default, PartialEq, Eq)] #[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct IdentIds { pub struct IdentIds {
by_ident: MutMap<InlinableString, IdentId>, by_ident: MutMap<Ident, IdentId>,
/// Each IdentId is an index into this Vec /// Each IdentId is an index into this Vec
by_id: Vec<InlinableString>, by_id: Vec<Ident>,
next_generated_name: u32, next_generated_name: u32,
} }
impl IdentIds { impl IdentIds {
pub fn idents(&self) -> impl Iterator<Item = (IdentId, &InlinableString)> { pub fn idents(&self) -> impl Iterator<Item = (IdentId, &Ident)> {
self.by_id self.by_id
.iter() .iter()
.enumerate() .enumerate()
.map(|(index, ident)| (IdentId(index as u32), ident)) .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 by_id = &mut self.by_id;
let ident_id = IdentId(by_id.len() as u32); let ident_id = IdentId(by_id.len() as u32);
@ -526,7 +535,7 @@ impl IdentIds {
ident_id 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) { match self.by_ident.get(name) {
Some(id) => *id, Some(id) => *id,
None => { None => {
@ -592,7 +601,7 @@ impl IdentIds {
/// This is used, for example, during canonicalization of an Expr::Closure /// This is used, for example, during canonicalization of an Expr::Closure
/// to generate a unique symbol to refer to that closure. /// to generate a unique symbol to refer to that closure.
pub fn gen_unique(&mut self) -> IdentId { 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. // without allocating an extra string along the way like this.
let ident = self.next_generated_name.to_string().into(); let ident = self.next_generated_name.to_string().into();
@ -601,11 +610,11 @@ impl IdentIds {
self.add(ident) 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) 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) self.by_id.get(id.0 as usize)
} }
} }
@ -636,13 +645,16 @@ macro_rules! define_builtins {
$ident_name.into(), $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); 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 { IdentIds {
@ -694,7 +706,7 @@ macro_rules! define_builtins {
let mut by_id = Vec::with_capacity(capacity); let mut by_id = Vec::with_capacity(capacity);
let mut insert_both = |id: ModuleId, name_str: &'static str| { 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) { if cfg!(debug_assertions) {
Self::insert_debug_name(id, &name); Self::insert_debug_name(id, &name);
@ -721,8 +733,8 @@ macro_rules! define_builtins {
let mut by_id = Vec::with_capacity(capacity); let mut by_id = Vec::with_capacity(capacity);
let mut insert_both = |id: ModuleId, name_str: &'static str| { let mut insert_both = |id: ModuleId, name_str: &'static str| {
let raw_name: InlinableString = name_str.into(); let raw_name: IdentStr = name_str.into();
let name = PQModuleName::Unqualified(raw_name); let name = PQModuleName::Unqualified(raw_name.into());
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
Self::insert_debug_name(id, &name); Self::insert_debug_name(id, &name);
@ -813,6 +825,9 @@ define_builtins! {
// a caller (wrapper) for comparison // a caller (wrapper) for comparison
21 GENERIC_COMPARE_REF: "#generic_compare_ref" 21 GENERIC_COMPARE_REF: "#generic_compare_ref"
// used to initialize parameters in borrow.rs
22 EMPTY_PARAM: "#empty_param"
} }
1 NUM: "Num" => { 1 NUM: "Num" => {
0 NUM_NUM: "Num" imported // the Num.Num type alias 0 NUM_NUM: "Num" imported // the Num.Num type alias
@ -915,7 +930,12 @@ define_builtins! {
97 NUM_INT_CAST: "intCast" 97 NUM_INT_CAST: "intCast"
98 NUM_MAX_I128: "maxI128" 98 NUM_MAX_I128: "maxI128"
99 NUM_IS_MULTIPLE_OF: "isMultipleOf" 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" => { 2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
@ -942,9 +962,10 @@ define_builtins! {
12 STR_FROM_UTF8: "fromUtf8" 12 STR_FROM_UTF8: "fromUtf8"
13 STR_UT8_PROBLEM: "Utf8Problem" // the Utf8Problem type alias 13 STR_UT8_PROBLEM: "Utf8Problem" // the Utf8Problem type alias
14 STR_UT8_BYTE_PROBLEM: "Utf8ByteProblem" // the Utf8ByteProblem type alias 14 STR_UT8_BYTE_PROBLEM: "Utf8ByteProblem" // the Utf8ByteProblem type alias
15 STR_TO_BYTES: "toBytes" 15 STR_TO_UTF8: "toUtf8"
16 STR_STARTS_WITH_CODE_POINT: "startsWithCodePoint" 16 STR_STARTS_WITH_CODE_PT: "startsWithCodePt"
17 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime 17 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime
18 STR_FROM_UTF8_RANGE: "fromUtf8Range"
} }
4 LIST: "List" => { 4 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias 0 LIST_LIST: "List" imported // the List.List type alias
@ -1012,8 +1033,6 @@ define_builtins! {
14 DICT_UNION: "union" 14 DICT_UNION: "union"
15 DICT_INTERSECTION: "intersection" 15 DICT_INTERSECTION: "intersection"
16 DICT_DIFFERENCE: "difference" 16 DICT_DIFFERENCE: "difference"
} }
7 SET: "Set" => { 7 SET: "Set" => {
0 SET_SET: "Set" imported // the Set.Set type alias 0 SET_SET: "Set" imported // the Set.Set type alias

View file

@ -19,6 +19,7 @@ morphic_lib = { path = "../../vendor/morphic_lib" }
bumpalo = { version = "3.6.1", features = ["collections"] } bumpalo = { version = "3.6.1", features = ["collections"] }
hashbrown = { version = "0.11.2", features = [ "bumpalo" ] } hashbrown = { version = "0.11.2", features = [ "bumpalo" ] }
ven_ena = { path = "../../vendor/ena" } ven_ena = { path = "../../vendor/ena" }
ven_graph = { path = "../../vendor/pathfinding" }
linked-hash-map = "0.5.4" linked-hash-map = "0.5.4"
[dev-dependencies] [dev-dependencies]

View file

@ -1,10 +1,10 @@
use morphic_lib::TypeContext; use morphic_lib::TypeContext;
use morphic_lib::{ use morphic_lib::{
BlockExpr, BlockId, CalleeSpecVar, ConstDefBuilder, ConstName, EntryPointName, ExprContext, BlockExpr, BlockId, CalleeSpecVar, ConstDefBuilder, ConstName, EntryPointName, ExprContext,
FuncDef, FuncDefBuilder, FuncName, ModDefBuilder, ModName, ProgramBuilder, Result, TypeId, FuncDef, FuncDefBuilder, FuncName, ModDefBuilder, ModName, ProgramBuilder, Result,
UpdateModeVar, ValueId, 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::low_level::LowLevel;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use std::convert::TryFrom; use std::convert::TryFrom;
@ -26,6 +26,26 @@ pub fn func_name_bytes(proc: &Proc) -> [u8; SIZE] {
const DEBUG: bool = false; const DEBUG: bool = false;
const SIZE: usize = if DEBUG { 50 } else { 16 }; 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>( pub fn func_name_bytes_help<'a, I>(
symbol: Symbol, symbol: Symbol,
argument_layouts: I, argument_layouts: I,
@ -44,24 +64,10 @@ where
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
for layout in argument_layouts { 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() hasher.finish()
}; };
@ -134,6 +140,8 @@ where
let entry_point_name = FuncName(ENTRY_POINT_NAME); let entry_point_name = FuncName(ENTRY_POINT_NAME);
m.add_func(entry_point_name, entry_point_function)?; m.add_func(entry_point_name, entry_point_function)?;
let mut type_definitions = MutSet::default();
// all other functions // all other functions
for proc in procs { for proc in procs {
let bytes = func_name_bytes(proc); 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)?; 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()? m.build()?
}; };
@ -195,7 +224,7 @@ fn build_entry_point(layout: crate::ir::ProcLayout, func_name: FuncName) -> Resu
Ok(spec) Ok(spec)
} }
fn proc_spec(proc: &Proc) -> Result<FuncDef> { fn proc_spec<'a>(proc: &Proc<'a>) -> Result<(FuncDef, MutSet<UnionLayout<'a>>)> {
let mut builder = FuncDefBuilder::new(); let mut builder = FuncDefBuilder::new();
let mut env = Env::default(); let mut env = Env::default();
@ -218,21 +247,22 @@ fn proc_spec(proc: &Proc) -> Result<FuncDef> {
let spec = builder.build(arg_type_id, ret_type_id, root)?; let spec = builder.build(arg_type_id, ret_type_id, root)?;
Ok(spec) Ok((spec, env.type_names))
} }
#[derive(Default)] #[derive(Default)]
struct Env { struct Env<'a> {
symbols: MutMap<Symbol, ValueId>, symbols: MutMap<Symbol, ValueId>,
join_points: MutMap<crate::ir::JoinPointId, morphic_lib::ContinuationId>, join_points: MutMap<crate::ir::JoinPointId, morphic_lib::ContinuationId>,
type_names: MutSet<UnionLayout<'a>>,
} }
fn stmt_spec( fn stmt_spec<'a>(
builder: &mut FuncDefBuilder, builder: &mut FuncDefBuilder,
env: &mut Env, env: &mut Env<'a>,
block: BlockId, block: BlockId,
layout: &Layout, layout: &Layout,
stmt: &Stmt, stmt: &Stmt<'a>,
) -> Result<ValueId> { ) -> Result<ValueId> {
use Stmt::*; use Stmt::*;
@ -259,30 +289,6 @@ fn stmt_spec(
Ok(result) 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 { Switch {
cond_symbol: _, cond_symbol: _,
cond_layout: _, cond_layout: _,
@ -390,7 +396,7 @@ fn stmt_spec(
let jpid = env.join_points[id]; let jpid = env.join_points[id];
builder.add_jump(block, jpid, argument, ret_type_id) builder.add_jump(block, jpid, argument, ret_type_id)
} }
Resume(_) | RuntimeError(_) => { RuntimeError(_) => {
let type_id = layout_spec(builder, layout)?; let type_id = layout_spec(builder, layout)?;
builder.add_terminate(block, type_id) builder.add_terminate(block, type_id)
@ -420,7 +426,27 @@ fn build_tuple_value(
builder.add_make_tuple(block, &value_ids) builder.add_make_tuple(block, &value_ids)
} }
fn build_tuple_type(builder: &mut FuncDefBuilder, layouts: &[Layout]) -> Result<TypeId> { #[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<TypeId> {
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<TypeId> {
let mut field_types = Vec::new(); let mut field_types = Vec::new();
for field in layouts.iter() { for field in layouts.iter() {
@ -739,15 +765,20 @@ fn lowlevel_spec(
builder.add_sub_block(block, sub_block) builder.add_sub_block(block, sub_block)
} }
NumToFloat => {
// just dream up a unit value
builder.add_make_tuple(block, &[])
}
Eq | NotEq => { Eq | NotEq => {
// just dream up a unit value // just dream up a unit value
builder.add_make_tuple(block, &[]) builder.add_make_tuple(block, &[])
} }
NumLte | NumLt | NumGt | NumGte => { NumLte | NumLt | NumGt | NumGte | NumCompare => {
// just dream up a unit value // just dream up a unit value
builder.add_make_tuple(block, &[]) builder.add_make_tuple(block, &[])
} }
ListLen => { ListLen | DictSize => {
// TODO should this touch the heap cell?
// just dream up a unit value // just dream up a unit value
builder.add_make_tuple(block, &[]) builder.add_make_tuple(block, &[])
} }
@ -773,9 +804,86 @@ fn lowlevel_spec(
builder.add_bag_insert(block, bag, to_insert)?; 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 => { _other => {
// println!("missing {:?}", _other);
// TODO overly pessimstic // TODO overly pessimstic
let arguments: Vec<_> = arguments.iter().map(|symbol| env.symbols[symbol]).collect(); 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<TypeId> {
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( fn build_variant_types(
builder: &mut FuncDefBuilder, builder: &mut impl TypeContext,
union_layout: &UnionLayout, union_layout: &UnionLayout,
) -> Result<Vec<TypeId>> { ) -> Result<Vec<TypeId>> {
use UnionLayout::*; use UnionLayout::*;
let mut result = Vec::new(); let mut result;
match union_layout { match union_layout {
NonRecursive(tags) => { NonRecursive(tags) => {
result = Vec::with_capacity(tags.len());
for tag in tags.iter() { for tag in tags.iter() {
result.push(build_tuple_type(builder, tag)?); result.push(build_tuple_type(builder, tag)?);
} }
} }
Recursive(_) => unreachable!(), Recursive(tags) => {
NonNullableUnwrapped(_) => unreachable!(), 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 { NullableWrapped {
nullable_id: _, nullable_id,
other_tags: _, other_tags: tags,
} => unreachable!(), } => {
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 { NullableUnwrapped {
nullable_id: _, nullable_id,
other_fields: _, other_fields: fields,
} => unreachable!(), } => {
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) Ok(result)
} }
#[allow(dead_code)]
fn worst_case_type(context: &mut impl TypeContext) -> Result<TypeId> { fn worst_case_type(context: &mut impl TypeContext) -> Result<TypeId> {
let cell = context.add_heap_cell_type(); let cell = context.add_heap_cell_type();
context.add_bag_type(cell) context.add_bag_type(cell)
} }
fn expr_spec( fn expr_spec<'a>(
builder: &mut FuncDefBuilder, builder: &mut FuncDefBuilder,
env: &mut Env, env: &mut Env<'a>,
block: BlockId, block: BlockId,
layout: &Layout, layout: &Layout<'a>,
expr: &Expr, expr: &Expr<'a>,
) -> Result<ValueId> { ) -> Result<ValueId> {
use Expr::*; use Expr::*;
match expr { match expr {
Literal(literal) => literal_spec(builder, block, literal), Literal(literal) => literal_spec(builder, block, literal),
Call(call) => call_spec(builder, env, block, layout, call), Call(call) => call_spec(builder, env, block, layout, call),
Tag { Reuse {
tag_layout, tag_layout,
tag_name: _, tag_name: _,
tag_id, tag_id,
union_size: _,
arguments, arguments,
} => match tag_layout { ..
}
| 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(_) => { UnionLayout::NonRecursive(_) => {
let value_id = build_tuple_value(builder, env, block, arguments)?; let value_id = build_tuple_value(builder, env, block, arguments)?;
let variant_types = build_variant_types(builder, tag_layout)?; return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id);
builder.add_make_union(block, &variant_types, *tag_id as u32, value_id)
} }
UnionLayout::Recursive(_) UnionLayout::NonNullableUnwrapped(_) => {
| UnionLayout::NonNullableUnwrapped(_) let value_id = builder.add_make_tuple(block, &[cell_id, data_id])?;
| UnionLayout::NullableWrapped { .. }
| UnionLayout::NullableUnwrapped { .. } => { let type_name_bytes = recursive_tag_union_name_bytes(tag_layout).as_bytes();
let result_type = worst_case_type(builder)?; let type_name = TypeName(&type_name_bytes);
let value_id = build_tuple_value(builder, env, block, arguments)?;
builder.add_unknown_with(block, &[value_id], result_type) 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), Struct(fields) => build_tuple_value(builder, env, block, fields),
UnionAtIndex { UnionAtIndex {
index, index,
@ -868,11 +1064,45 @@ fn expr_spec(
builder.add_get_tuple_field(block, tuple_value_id, index) builder.add_get_tuple_field(block, tuple_value_id, index)
} }
_ => { UnionLayout::Recursive(_)
// for the moment recursive tag unions don't quite work | UnionLayout::NullableUnwrapped { .. }
let value_id = env.symbols[structure]; | UnionLayout::NullableWrapped { .. } => {
let result_type = layout_spec(builder, layout)?; let index = (*index) as u32;
builder.add_unknown_with(block, &[value_id], result_type) 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 { StructAtIndex {
@ -915,8 +1145,12 @@ fn expr_spec(
Err(()) => unreachable!("empty array does not have a list layout"), Err(()) => unreachable!("empty array does not have a list layout"),
} }
} }
Reuse { .. } => todo!("currently unused"), Reset(symbol) => {
Reset(_) => todo!("currently unused"), let type_id = layout_spec(builder, layout)?;
let value_id = env.symbols[symbol];
builder.add_unknown_with(block, &[value_id], type_id)
}
RuntimeErrorFunction(_) => { RuntimeErrorFunction(_) => {
let type_id = layout_spec(builder, layout)?; let type_id = layout_spec(builder, layout)?;
@ -939,43 +1173,70 @@ fn literal_spec(
} }
} }
fn layout_spec(builder: &mut FuncDefBuilder, layout: &Layout) -> Result<TypeId> { fn layout_spec(builder: &mut impl TypeContext, layout: &Layout) -> Result<TypeId> {
layout_spec_help(builder, layout, &WhenRecursive::Unreachable)
}
fn layout_spec_help(
builder: &mut impl TypeContext,
layout: &Layout,
when_recursive: &WhenRecursive,
) -> Result<TypeId> {
use Layout::*; use Layout::*;
match layout { match layout {
Builtin(builtin) => builtin_spec(builder, builtin), Builtin(builtin) => builtin_spec(builder, builtin, when_recursive),
Struct(fields) => build_tuple_type(builder, fields), Struct(fields) => build_recursive_tuple_type(builder, fields, when_recursive),
Union(union_layout) => match union_layout { Union(union_layout) => {
UnionLayout::NonRecursive(_) => {
let variant_types = build_variant_types(builder, union_layout)?; let variant_types = build_variant_types(builder, union_layout)?;
builder.add_union_type(&variant_types)
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))
}
}
}
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))
} }
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 => worst_case_type(builder), },
Closure(_, lambda_set, _) => layout_spec(builder, &lambda_set.runtime_representation()),
} }
} }
fn builtin_spec(builder: &mut FuncDefBuilder, builtin: &Builtin) -> Result<TypeId> { fn builtin_spec(
builder: &mut impl TypeContext,
builtin: &Builtin,
when_recursive: &WhenRecursive,
) -> Result<TypeId> {
use Builtin::*; use Builtin::*;
match builtin { match builtin {
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize => builder.add_tuple_type(&[]), 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), Str | EmptyStr => str_type(builder),
Dict(key_layout, value_layout) => { Dict(key_layout, value_layout) => {
let value_type = layout_spec(builder, value_layout)?; let value_type = layout_spec_help(builder, value_layout, when_recursive)?;
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 element_type = builder.add_tuple_type(&[key_type, value_type])?;
let cell = builder.add_heap_cell_type(); let cell = builder.add_heap_cell_type();
@ -984,7 +1245,7 @@ fn builtin_spec(builder: &mut FuncDefBuilder, builtin: &Builtin) -> Result<TypeI
} }
Set(key_layout) => { Set(key_layout) => {
let value_type = builder.add_tuple_type(&[])?; 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 element_type = builder.add_tuple_type(&[key_type, value_type])?;
let cell = builder.add_heap_cell_type(); let cell = builder.add_heap_cell_type();
@ -992,7 +1253,7 @@ fn builtin_spec(builder: &mut FuncDefBuilder, builtin: &Builtin) -> Result<TypeI
builder.add_tuple_type(&[cell, bag]) builder.add_tuple_type(&[cell, bag])
} }
List(element_layout) => { List(element_layout) => {
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 cell = builder.add_heap_cell_type();
let bag = builder.add_bag_type(element_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]) builder.add_make_tuple(block, &[cell, bag])
} }
fn new_dict(
builder: &mut FuncDefBuilder,
block: BlockId,
key_type: TypeId,
value_type: TypeId,
) -> Result<ValueId> {
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<ValueId> { fn new_static_string(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {
let module = MOD_APP; let module = MOD_APP;

View file

@ -2,7 +2,7 @@ use crate::ir::{Expr, JoinPointId, Param, Proc, ProcLayout, Stmt};
use crate::layout::Layout; use crate::layout::Layout;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; 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::low_level::LowLevel;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -10,18 +10,23 @@ pub const OWNED: bool = false;
pub const BORROWED: bool = true; pub const BORROWED: bool = true;
fn should_borrow_layout(layout: &Layout) -> bool { fn should_borrow_layout(layout: &Layout) -> bool {
match layout { layout.is_refcounted()
Layout::Closure(_, _, _) => false,
_ => layout.is_refcounted(),
}
} }
pub fn infer_borrow<'a>( pub fn infer_borrow<'a>(
arena: &'a Bump, arena: &'a Bump,
procs: &MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, procs: &MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> ParamMap<'a> { ) -> ParamMap<'a> {
let mut param_map = ParamMap { // intern the layouts
items: MutMap::default(),
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 { for (key, proc) in procs {
@ -33,10 +38,101 @@ pub fn infer_borrow<'a>(
param_set: MutSet::default(), param_set: MutSet::default(),
owned: MutMap::default(), owned: MutMap::default(),
modified: false, modified: false,
param_map,
arena, arena,
}; };
// 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)
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(),
};
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);
}
}
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 // This is a fixed-point analysis
// //
// all functions initiall own all their parameters // all functions initiall own all their parameters
@ -45,12 +141,8 @@ pub fn infer_borrow<'a>(
// //
// when the signatures no longer change, the analysis stops and returns the signatures // when the signatures no longer change, the analysis stops and returns the signatures
loop { loop {
// sort the symbols (roughly) in definition order. for (proc, param_offset) in group.iter() {
// TODO in the future I think we need to do this properly, and group env.collect_proc(&mut param_map, proc, *param_offset);
// mutually recursive functions (or just make all their arguments owned)
for (key, proc) in procs {
env.collect_proc(proc, key.1);
} }
if !env.modified { if !env.modified {
@ -61,54 +153,120 @@ pub fn infer_borrow<'a>(
env.modified = false; env.modified = false;
} }
} }
}
env.param_map param_map
} }
#[derive(Debug, PartialEq, Eq, Hash, Clone)] #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum Key<'a> { pub struct ParamOffset(usize);
Declaration(Symbol, ProcLayout<'a>),
JoinPoint(JoinPointId), impl From<ParamOffset> 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> { pub struct ParamMap<'a> {
items: MutMap<Key<'a>, &'a [Param<'a>]>, /// 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.
impl<'a> IntoIterator for ParamMap<'a> { ///
type Item = (Key<'a>, &'a [Param<'a>]); /// - the map above gives the index of the first parameter for the function
type IntoIter = <std::collections::HashMap<Key<'a>, &'a [Param<'a>]> as IntoIterator>::IntoIter; /// - the length of the ProcLayout's argument field gives the total number of parameters
///
fn into_iter(self) -> Self::IntoIter { /// These can be read by taking a slice into this array, and can also be updated in-place
self.items.into_iter() declarations: Vec<'a, Param<'a>>,
} join_points: MutMap<JoinPointId, &'a [Param<'a>]>,
}
impl<'a> IntoIterator for &'a ParamMap<'a> {
type Item = (&'a Key<'a>, &'a &'a [Param<'a>]);
type IntoIter =
<&'a std::collections::HashMap<Key<'a>, &'a [Param<'a>]> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.items.iter()
}
} }
impl<'a> ParamMap<'a> { impl<'a> ParamMap<'a> {
pub fn get_symbol(&self, symbol: Symbol, layout: ProcLayout<'a>) -> Option<&'a [Param<'a>]> { pub fn get_param_offset(&self, symbol: Symbol, layout: ProcLayout<'a>) -> ParamOffset {
let key = Key::Declaration(symbol, layout); self.declaration_to_index.get_param_offset(symbol, layout)
self.items.get(&key).copied()
} }
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, Some(slice) => slice,
None => unreachable!("join point not in param map: {:?}", id), None => unreachable!("join point not in param map: {:?}", id),
} }
} }
pub fn iter_symbols(&'a self) -> impl Iterator<Item = &'a Symbol> {
self.declaration_to_index.elements.iter().map(|t| &t.0 .0)
}
} }
impl<'a> ParamMap<'a> { impl<'a> ParamMap<'a> {
@ -156,11 +314,16 @@ impl<'a> ParamMap<'a> {
self.visit_proc_always_owned(arena, proc, key); self.visit_proc_always_owned(arena, proc, key);
return; return;
} }
let already_in_there = self.items.insert(
Key::Declaration(proc.name, key.1), let index: usize = self.get_param_offset(key.0, key.1).into();
Self::init_borrow_args(arena, proc.args),
); for (i, param) in Self::init_borrow_args(arena, proc.args)
debug_assert!(already_in_there.is_none()); .iter()
.copied()
.enumerate()
{
self.declarations[index + i] = param;
}
self.visit_stmt(arena, proc.name, &proc.body); self.visit_stmt(arena, proc.name, &proc.body);
} }
@ -171,11 +334,15 @@ impl<'a> ParamMap<'a> {
proc: &Proc<'a>, proc: &Proc<'a>,
key: (Symbol, ProcLayout<'a>), key: (Symbol, ProcLayout<'a>),
) { ) {
let already_in_there = self.items.insert( let index: usize = self.get_param_offset(key.0, key.1).into();
Key::Declaration(proc.name, key.1),
Self::init_borrow_args_always_owned(arena, proc.args), for (i, param) in Self::init_borrow_args_always_owned(arena, proc.args)
); .iter()
debug_assert!(already_in_there.is_none()); .copied()
.enumerate()
{
self.declarations[index + i] = param;
}
self.visit_stmt(arena, proc.name, &proc.body); self.visit_stmt(arena, proc.name, &proc.body);
} }
@ -193,10 +360,8 @@ impl<'a> ParamMap<'a> {
remainder: v, remainder: v,
body: b, body: b,
} => { } => {
let already_in_there = self self.join_points
.items .insert(*j, Self::init_borrow_params(arena, xs));
.insert(Key::JoinPoint(*j), Self::init_borrow_params(arena, xs));
debug_assert!(already_in_there.is_none());
stack.push(v); stack.push(v);
stack.push(b); stack.push(b);
@ -204,10 +369,6 @@ impl<'a> ParamMap<'a> {
Let(_, _, _, cont) => { Let(_, _, _, cont) => {
stack.push(cont); stack.push(cont);
} }
Invoke { pass, fail, .. } => {
stack.push(pass);
stack.push(fail);
}
Switch { Switch {
branches, branches,
default_branch, default_branch,
@ -218,7 +379,7 @@ impl<'a> ParamMap<'a> {
} }
Refcounting(_, _) => unreachable!("these have not been introduced yet"), Refcounting(_, _) => unreachable!("these have not been introduced yet"),
Ret(_) | Resume(_) | Jump(_, _) | RuntimeError(_) => { Ret(_) | Jump(_, _) | RuntimeError(_) => {
// these are terminal, do nothing // these are terminal, do nothing
} }
} }
@ -233,7 +394,6 @@ struct BorrowInfState<'a> {
param_set: MutSet<Symbol>, param_set: MutSet<Symbol>,
owned: MutMap<Symbol, MutSet<Symbol>>, owned: MutMap<Symbol, MutSet<Symbol>>,
modified: bool, modified: bool,
param_map: ParamMap<'a>,
arena: &'a Bump, arena: &'a Bump,
} }
@ -241,14 +401,30 @@ impl<'a> BorrowInfState<'a> {
pub fn own_var(&mut self, x: Symbol) { pub fn own_var(&mut self, x: Symbol) {
let current = self.owned.get_mut(&self.current_proc).unwrap(); let current = self.owned.get_mut(&self.current_proc).unwrap();
if current.contains(&x) { if current.insert(x) {
// do nothing // entered if key was not yet present. If so, the set is modified,
} else { // hence we set this flag
current.insert(x);
self.modified = true; 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 { fn is_owned(&self, x: Symbol) -> bool {
match self.owned.get(&self.current_proc) { match self.owned.get(&self.current_proc) {
None => unreachable!( None => unreachable!(
@ -259,28 +435,50 @@ impl<'a> BorrowInfState<'a> {
} }
} }
fn update_param_map(&mut self, k: Key<'a>) { fn update_param_map_help(&mut self, ps: &[Param<'a>]) -> &'a [Param<'a>] {
let arena = self.arena; let mut new_ps = Vec::with_capacity_in(ps.len(), self.arena);
if let Some(ps) = self.param_map.items.get(&k) { new_ps.extend(ps.iter().map(|p| {
let ps = Vec::from_iter_in(
ps.iter().map(|p| {
if !p.borrow { if !p.borrow {
p.clone() *p
} else if self.is_owned(p.symbol) { } else if self.is_owned(p.symbol) {
self.modified = true; self.modified = true;
let mut p = p.clone(); let mut p = *p;
p.borrow = false; p.borrow = false;
p p
} else { } else {
p.clone() *p
} }
}), }));
arena,
);
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` /// This looks at an application `f x1 x2 x3`
@ -341,13 +539,13 @@ impl<'a> BorrowInfState<'a> {
} }
} }
/// This looks at the assignement /// This looks at the assignment
/// ///
/// let z = e in ... /// let z = e in ...
/// ///
/// and determines whether z and which of the symbols used in e /// and determines whether z and which of the symbols used in e
/// must be taken as owned parameters /// 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::*; use crate::ir::CallType::*;
let crate::ir::Call { let crate::ir::Call {
@ -365,8 +563,7 @@ impl<'a> BorrowInfState<'a> {
let top_level = ProcLayout::new(self.arena, arg_layouts, *ret_layout); let top_level = ProcLayout::new(self.arena, arg_layouts, *ret_layout);
// get the borrow signature of the applied function // get the borrow signature of the applied function
let ps = self let ps = param_map
.param_map
.get_symbol(*name, top_level) .get_symbol(*name, top_level)
.expect("function is defined"); .expect("function is defined");
@ -382,6 +579,7 @@ impl<'a> BorrowInfState<'a> {
ps.len(), ps.len(),
arguments.len() arguments.len()
); );
self.own_args_using_params(arguments, ps); self.own_args_using_params(arguments, ps);
} }
@ -412,7 +610,7 @@ impl<'a> BorrowInfState<'a> {
match op { match op {
ListMap | ListKeepIf | ListKeepOks | ListKeepErrs => { 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) => { Some(function_ps) => {
// own the list if the function wants to own the element // own the list if the function wants to own the element
if !function_ps[0].borrow { if !function_ps[0].borrow {
@ -428,7 +626,7 @@ impl<'a> BorrowInfState<'a> {
} }
} }
ListMapWithIndex => { ListMapWithIndex => {
match self.param_map.get_symbol(arguments[1], closure_layout) { match param_map.get_symbol(arguments[1], closure_layout) {
Some(function_ps) => { Some(function_ps) => {
// own the list if the function wants to own the element // own the list if the function wants to own the element
if !function_ps[1].borrow { if !function_ps[1].borrow {
@ -443,7 +641,7 @@ impl<'a> BorrowInfState<'a> {
None => unreachable!(), 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) => { Some(function_ps) => {
// own the lists if the function wants to own the element // own the lists if the function wants to own the element
if !function_ps[0].borrow { if !function_ps[0].borrow {
@ -461,7 +659,7 @@ impl<'a> BorrowInfState<'a> {
} }
None => unreachable!(), 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) => { Some(function_ps) => {
// own the lists if the function wants to own the element // own the lists if the function wants to own the element
if !function_ps[0].borrow { if !function_ps[0].borrow {
@ -482,7 +680,7 @@ impl<'a> BorrowInfState<'a> {
None => unreachable!(), None => unreachable!(),
}, },
ListSortWith => { ListSortWith => {
match self.param_map.get_symbol(arguments[1], closure_layout) { match param_map.get_symbol(arguments[1], closure_layout) {
Some(function_ps) => { Some(function_ps) => {
// always own the input list // always own the input list
self.own_var(arguments[0]); self.own_var(arguments[0]);
@ -496,7 +694,7 @@ impl<'a> BorrowInfState<'a> {
} }
} }
ListWalk | ListWalkUntil | ListWalkBackwards | DictWalk => { 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) => { Some(function_ps) => {
// own the data structure if the function wants to own the element // own the data structure if the function wants to own the element
if !function_ps[0].borrow { 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::*; use Expr::*;
match e { match e {
@ -566,50 +764,44 @@ impl<'a> BorrowInfState<'a> {
self.own_var(z); self.own_var(z);
} }
Call(call) => self.collect_call(z, call), Call(call) => self.collect_call(param_map, z, call),
Literal(_) | RuntimeErrorFunction(_) => {} Literal(_) | RuntimeErrorFunction(_) => {}
StructAtIndex { structure: x, .. } => { StructAtIndex { structure: x, .. } => {
// if the structure (record/tag/array) is owned, the extracted value is // if the structure (record/tag/array) is owned, the extracted value is
if self.is_owned(*x) { self.if_is_owned_then_own(*x, z);
self.own_var(z);
}
// if the extracted value is owned, the structure must be too // if the extracted value is owned, the structure must be too
if self.is_owned(z) { self.if_is_owned_then_own(z, *x);
self.own_var(*x);
}
} }
UnionAtIndex { structure: x, .. } => { UnionAtIndex { structure: x, .. } => {
// if the structure (record/tag/array) is owned, the extracted value is // if the structure (record/tag/array) is owned, the extracted value is
if self.is_owned(*x) { self.if_is_owned_then_own(*x, z);
self.own_var(z);
}
// if the extracted value is owned, the structure must be too // if the extracted value is owned, the structure must be too
if self.is_owned(z) { self.if_is_owned_then_own(z, *x);
self.own_var(*x);
}
} }
GetTagId { structure: x, .. } => { GetTagId { structure: x, .. } => {
// if the structure (record/tag/array) is owned, the extracted value is // if the structure (record/tag/array) is owned, the extracted value is
if self.is_owned(*x) { self.if_is_owned_then_own(*x, z);
self.own_var(z);
}
// if the extracted value is owned, the structure must be too // if the extracted value is owned, the structure must be too
if self.is_owned(z) { self.if_is_owned_then_own(z, *x);
self.own_var(*x);
}
} }
} }
} }
#[allow(clippy::many_single_char_names)] #[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 ( if let (
Expr::Call(crate::ir::Call { Expr::Call(crate::ir::Call {
call_type: call_type:
@ -630,7 +822,7 @@ impl<'a> BorrowInfState<'a> {
if self.current_proc == *g && x == *z { if self.current_proc == *g && x == *z {
// anonymous functions (for which the ps may not be known) // anonymous functions (for which the ps may not be known)
// can never be tail-recursive, so this is fine // can never be tail-recursive, so this is fine
if let Some(ps) = self.param_map.get_symbol(*g, top_level) { if let Some(ps) = param_map.get_symbol(*g, top_level) {
self.own_params_using_args(ys, ps) 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::*; use Stmt::*;
match stmt { match stmt {
@ -661,38 +853,39 @@ impl<'a> BorrowInfState<'a> {
} => { } => {
let old = self.param_set.clone(); let old = self.param_set.clone();
self.update_param_set(ys); self.update_param_set(ys);
self.collect_stmt(v); self.collect_stmt(param_map, v);
self.param_set = old; 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) => { Let(x, v, _, mut b) => {
self.collect_stmt(b); let mut stack = Vec::new_in(self.arena);
self.collect_expr(*x, v);
self.preserve_tail_call(*x, v, b); stack.push((*x, v));
while let Stmt::Let(symbol, expr, _, tail) = b {
b = tail;
stack.push((*symbol, expr));
} }
Invoke { self.collect_stmt(param_map, b);
symbol,
call,
layout: _,
pass,
fail,
exception_id: _,
} => {
self.collect_stmt(pass);
self.collect_stmt(fail);
self.collect_call(*symbol, call); let mut it = stack.into_iter().rev();
// TODO how to preserve the tail call of an invoke? // collect the final expr, and see if we need to preserve a tail call
// self.preserve_tail_call(*x, v, b); 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) => { 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 // for making sure the join point can reuse
self.own_args_using_params(ys, ps); self.own_args_using_params(ys, ps);
@ -706,19 +899,24 @@ impl<'a> BorrowInfState<'a> {
.. ..
} => { } => {
for (_, _, b) in branches.iter() { 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"), Refcounting(_, _) => unreachable!("these have not been introduced yet"),
Ret(_) | RuntimeError(_) | Resume(_) => { Ret(_) | RuntimeError(_) => {
// these are terminal, do nothing // 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 old = self.param_set.clone();
let ys = Vec::from_iter_in(proc.args.iter().map(|t| t.1), self.arena).into_bump_slice(); 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 // ensure that current_proc is in the owned map
self.owned.entry(proc.name).or_default(); self.owned.entry(proc.name).or_default();
self.collect_stmt(&proc.body); self.collect_stmt(param_map, &proc.body);
self.update_param_map(Key::Declaration(proc.name, layout)); self.update_param_map_declaration(param_map, param_offset, proc.args.len());
self.param_set = old; 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 NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound
| NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin
| NumIntCast => arena.alloc_slice_copy(&[irrelevant]), | 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]), 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]), 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]), StrFromInt | StrFromFloat => arena.alloc_slice_copy(&[irrelevant]),
Hash => arena.alloc_slice_copy(&[borrowed, irrelevant]), Hash => arena.alloc_slice_copy(&[borrowed, irrelevant]),
DictSize => arena.alloc_slice_copy(&[borrowed]), 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]), ExpectTrue => arena.alloc_slice_copy(&[irrelevant]),
} }
} }
fn make_successor_mapping<'a>(
arena: &'a Bump,
procs: &MutMap<(Symbol, ProcLayout<'_>), Proc<'a>>,
) -> MutMap<Symbol, Vec<'a, Symbol>> {
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
}
}
}
}

View file

@ -22,7 +22,8 @@ fn compile<'a>(raw_branches: Vec<(Guard<'a>, Pattern<'a>, u64)>) -> DecisionTree
.into_iter() .into_iter()
.map(|(guard, pattern, index)| Branch { .map(|(guard, pattern, index)| Branch {
goal: index, goal: index,
patterns: vec![(Vec::new(), guard, pattern)], guard,
patterns: vec![(Vec::new(), pattern)],
}) })
.collect(); .collect();
@ -33,9 +34,8 @@ fn compile<'a>(raw_branches: Vec<(Guard<'a>, Pattern<'a>, u64)>) -> DecisionTree
pub enum Guard<'a> { pub enum Guard<'a> {
NoGuard, NoGuard,
Guard { Guard {
/// Symbol that stores a boolean /// pattern
/// when true this branch is picked, otherwise skipped pattern: Pattern<'a>,
symbol: Symbol,
/// after assigning to symbol, the stmt jumps to this label /// after assigning to symbol, the stmt jumps to this label
id: JoinPointId, id: JoinPointId,
stmt: Stmt<'a>, stmt: Stmt<'a>,
@ -60,22 +60,19 @@ enum DecisionTree<'a> {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
enum GuardedTest<'a> { 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 -> ...` // e.g. `_ if True -> ...`
GuardedNoTest { GuardedNoTest {
/// pattern
pattern: Pattern<'a>,
/// after assigning to symbol, the stmt jumps to this label /// after assigning to symbol, the stmt jumps to this label
id: JoinPointId, id: JoinPointId,
/// body
stmt: Stmt<'a>, stmt: Stmt<'a>,
}, },
TestNotGuarded { TestNotGuarded {
test: Test<'a>, test: Test<'a>,
}, },
Placeholder,
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -136,16 +133,16 @@ impl<'a> Hash for Test<'a> {
impl<'a> Hash for GuardedTest<'a> { impl<'a> Hash for GuardedTest<'a> {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
match self { match self {
GuardedTest::TestGuarded { test, .. } => { GuardedTest::GuardedNoTest { id, .. } => {
state.write_u8(1);
id.hash(state);
}
GuardedTest::TestNotGuarded { test } => {
state.write_u8(0); state.write_u8(0);
test.hash(state); test.hash(state);
} }
GuardedTest::GuardedNoTest { .. } => { GuardedTest::Placeholder => {
state.write_u8(1);
}
GuardedTest::TestNotGuarded { test } => {
state.write_u8(2); state.write_u8(2);
test.hash(state);
} }
} }
} }
@ -156,22 +153,70 @@ impl<'a> Hash for GuardedTest<'a> {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
struct Branch<'a> { struct Branch<'a> {
goal: Label, goal: Label,
patterns: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>, guard: Guard<'a>,
patterns: Vec<(Vec<PathInstruction>, Pattern<'a>)>,
} }
fn to_decision_tree(raw_branches: Vec<Branch>) -> DecisionTree { fn to_decision_tree(raw_branches: Vec<Branch>) -> DecisionTree {
let branches: Vec<_> = raw_branches.into_iter().map(flatten_patterns).collect(); let branches: Vec<_> = raw_branches.into_iter().map(flatten_patterns).collect();
debug_assert!(!branches.is_empty());
match check_for_match(&branches) { match check_for_match(&branches) {
Some(goal) => DecisionTree::Match(goal), Match::Exact(goal) => DecisionTree::Match(goal),
None => {
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` // must clone here to release the borrow on `branches`
let path = pick_path(&branches).clone(); let path = pick_path(&branches).clone();
let bs = branches.clone();
let (edges, fallback) = gather_edges(branches, &path); let (edges, fallback) = gather_edges(branches, &path);
let mut decision_edges: Vec<_> = edges let mut decision_edges: Vec<_> = edges
.into_iter() .into_iter()
.map(|(a, b)| (a, to_decision_tree(b))) .map(|(test, branches)| {
if bs == branches {
panic!();
} else {
(test, to_decision_tree(branches))
}
})
.collect(); .collect();
match (decision_edges.as_slice(), fallback.as_slice()) { match (decision_edges.as_slice(), fallback.as_slice()) {
@ -181,21 +226,55 @@ fn to_decision_tree(raw_branches: Vec<Branch>) -> DecisionTree {
// get the `_decision_tree` without cloning // get the `_decision_tree` without cloning
decision_edges.pop().unwrap().1 decision_edges.pop().unwrap().1
} }
(_, []) => DecisionTree::Decision { (_, []) => break_out_guard(path, decision_edges, None),
path,
edges: decision_edges,
default: None,
},
([], _) => { ([], _) => {
// should be guaranteed by the patterns // should be guaranteed by the patterns
debug_assert!(!fallback.is_empty()); debug_assert!(!fallback.is_empty());
to_decision_tree(fallback) to_decision_tree(fallback)
} }
(_, _) => DecisionTree::Decision { (_, _) => break_out_guard(
path, path,
edges: decision_edges, decision_edges,
default: Some(Box::new(to_decision_tree(fallback))), Some(Box::new(to_decision_tree(fallback))),
),
}
}
}
}
/// Give a guard it's own Decision
fn break_out_guard<'a>(
path: Vec<PathInstruction>,
mut edges: Vec<(GuardedTest<'a>, DecisionTree<'a>)>,
default: Option<Box<DecisionTree<'a>>>,
) -> 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(); let length = tests.len();
debug_assert!(length > 0); debug_assert!(length > 0);
let no_guard = tests
.iter()
.all(|t| matches!(t, GuardedTest::TestNotGuarded { .. }));
match tests.last().unwrap() { match tests.last().unwrap() {
GuardedTest::TestGuarded { .. } => false, GuardedTest::Placeholder => false,
GuardedTest::GuardedNoTest { .. } => 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 { Branch {
goal: branch.goal,
patterns: result, patterns: result,
..branch
} }
} }
fn flatten<'a>( fn flatten<'a>(
path_pattern: (Vec<PathInstruction>, Guard<'a>, Pattern<'a>), path_pattern: (Vec<PathInstruction>, Pattern<'a>),
path_patterns: &mut Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>, path_patterns: &mut Vec<(Vec<PathInstruction>, Pattern<'a>)>,
) { ) {
match path_pattern.2 { match path_pattern.1 {
Pattern::AppliedTag { Pattern::AppliedTag {
union, union,
arguments, arguments,
@ -257,7 +340,6 @@ fn flatten<'a>(
// NOTE here elm will unbox, but we don't use that // NOTE here elm will unbox, but we don't use that
path_patterns.push(( path_patterns.push((
path, path,
path_pattern.1.clone(),
Pattern::AppliedTag { Pattern::AppliedTag {
union, union,
arguments, arguments,
@ -274,15 +356,7 @@ fn flatten<'a>(
tag_id, tag_id,
}); });
flatten( flatten((new_path, arg_pattern.clone()), path_patterns);
(
new_path,
// same guard here?
path_pattern.1.clone(),
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 /// 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 /// variables to "how to get their value". So a pattern like (Just (x,_)) will give
/// us something like ("x" => value.0.0) /// us something like ("x" => value.0.0)
fn check_for_match(branches: &[Branch]) -> Option<Label> {
enum Match {
Exact(Label),
GuardOnly,
None,
}
fn check_for_match(branches: &[Branch]) -> Match {
match branches.get(0) { match branches.get(0) {
Some(Branch { goal, patterns }) Some(Branch {
if patterns goal,
.iter() patterns,
.all(|(_, guard, pattern)| guard.is_none() && !needs_tests(pattern)) => guard,
{ }) if patterns.iter().all(|(_, pattern)| !needs_tests(pattern)) => {
Some(*goal) if guard.is_none() {
Match::Exact(*goal)
} else {
Match::GuardOnly
} }
_ => None, }
_ => Match::None,
} }
} }
/// GATHER OUTGOING EDGES /// GATHER OUTGOING EDGES
// my understanding: branches that we could jump to based on the pattern at the current path
fn gather_edges<'a>( fn gather_edges<'a>(
branches: Vec<Branch<'a>>, branches: Vec<Branch<'a>>,
path: &[PathInstruction], path: &[PathInstruction],
@ -351,7 +437,7 @@ fn tests_at_path<'a>(
let mut all_tests = Vec::new(); let mut all_tests = Vec::new();
for branch in branches { for branch in branches {
test_at_path(selected_path, branch, &mut all_tests); all_tests.extend(test_at_path(selected_path, branch));
} }
// The rust HashMap also uses equality, here we really want to use the custom hash function // The rust HashMap also uses equality, here we really want to use the custom hash function
@ -382,28 +468,26 @@ fn tests_at_path<'a>(
fn test_at_path<'a>( fn test_at_path<'a>(
selected_path: &[PathInstruction], selected_path: &[PathInstruction],
branch: &Branch<'a>, branch: &Branch<'a>,
guarded_tests: &mut Vec<GuardedTest<'a>>, ) -> Option<GuardedTest<'a>> {
) {
use Pattern::*; use Pattern::*;
use Test::*; use Test::*;
match branch match branch
.patterns .patterns
.iter() .iter()
.find(|(path, _, _)| path == selected_path) .find(|(path, _)| path == selected_path)
{ {
None => {} None => None,
Some((_, guard, pattern)) => { Some((_, pattern)) => {
let test = match pattern { let test = match pattern {
Identifier(_) | Underscore => { Identifier(_) | Underscore => {
if let Guard::Guard { id, stmt, .. } = guard { if let Guard::Guard { .. } = &branch.guard {
guarded_tests.push(GuardedTest::GuardedNoTest { // no tests for this pattern remain, but we cannot discard it yet
stmt: stmt.clone(), // because it has a guard!
id: *id, return Some(GuardedTest::Placeholder);
}); } else {
return None;
} }
return;
} }
RecordDestructure(destructs, _) => { RecordDestructure(destructs, _) => {
@ -475,23 +559,16 @@ fn test_at_path<'a>(
StrLiteral(v) => IsStr(v.clone()), StrLiteral(v) => IsStr(v.clone()),
}; };
let guarded_test = if let Guard::Guard { id, stmt, .. } = guard { let guarded_test = GuardedTest::TestNotGuarded { test };
GuardedTest::TestGuarded {
test,
stmt: stmt.clone(),
id: *id,
}
} else {
GuardedTest::TestNotGuarded { test }
};
guarded_tests.push(guarded_test); Some(guarded_test)
} }
} }
} }
/// BUILD EDGES /// BUILD EDGES
// understanding: if the test is successful, where could we go?
fn edges_for<'a>( fn edges_for<'a>(
path: &[PathInstruction], path: &[PathInstruction],
branches: Vec<Branch<'a>>, branches: Vec<Branch<'a>>,
@ -499,8 +576,22 @@ fn edges_for<'a>(
) -> (GuardedTest<'a>, Vec<Branch<'a>>) { ) -> (GuardedTest<'a>, Vec<Branch<'a>>) {
let mut new_branches = Vec::new(); let mut new_branches = Vec::new();
for branch in branches.iter() { // if we test for a guard, skip all branches until one that has a guard
to_relevant_branch(&test, path, branch, &mut new_branches);
let it = match test {
GuardedTest::GuardedNoTest { .. } | GuardedTest::Placeholder => {
let index = branches
.iter()
.position(|b| !b.guard.is_none())
.expect("if testing for a guard, one branch must have a guard");
branches[index..].iter()
}
GuardedTest::TestNotGuarded { .. } => branches.iter(),
};
for branch in it {
new_branches.extend(to_relevant_branch(&test, path, branch));
} }
(test, new_branches) (test, new_branches)
@ -510,59 +601,38 @@ fn to_relevant_branch<'a>(
guarded_test: &GuardedTest<'a>, guarded_test: &GuardedTest<'a>,
path: &[PathInstruction], path: &[PathInstruction],
branch: &Branch<'a>, branch: &Branch<'a>,
new_branches: &mut Vec<Branch<'a>>, ) -> Option<Branch<'a>> {
) {
// TODO remove clone // TODO remove clone
match extract(path, branch.patterns.clone()) { match extract(path, branch.patterns.clone()) {
Extract::NotFound => { Extract::NotFound => Some(branch.clone()),
new_branches.push(branch.clone());
}
Extract::Found { Extract::Found {
start, start,
found_pattern: (guard, pattern), found_pattern: pattern,
end, end,
} => { } => match guarded_test {
let actual_test = match guarded_test { GuardedTest::Placeholder | GuardedTest::GuardedNoTest { .. } => {
GuardedTest::TestGuarded { test, .. } => test, // if there is no test, the pattern should not require any
GuardedTest::GuardedNoTest { .. } => { debug_assert!(
let mut new_branch = branch.clone(); matches!(pattern, Pattern::Identifier(_) | Pattern::Underscore,),
"{:?}",
pattern,
);
// guards can/should only occur at the top level. When we recurse on these Some(branch.clone())
// branches, the guard is not relevant any more. Not setthing the guard to None
// leads to infinite recursion.
new_branch.patterns.iter_mut().for_each(|(_, guard, _)| {
*guard = Guard::NoGuard;
});
new_branches.push(new_branch);
return;
}
GuardedTest::TestNotGuarded { test } => test,
};
if let Some(mut new_branch) =
to_relevant_branch_help(actual_test, path, start, end, branch, guard, pattern)
{
// guards can/should only occur at the top level. When we recurse on these
// branches, the guard is not relevant any more. Not setthing the guard to None
// leads to infinite recursion.
new_branch.patterns.iter_mut().for_each(|(_, guard, _)| {
*guard = Guard::NoGuard;
});
new_branches.push(new_branch);
} }
GuardedTest::TestNotGuarded { test } => {
to_relevant_branch_help(test, path, start, end, branch, pattern)
} }
},
} }
} }
fn to_relevant_branch_help<'a>( fn to_relevant_branch_help<'a>(
test: &Test<'a>, test: &Test<'a>,
path: &[PathInstruction], path: &[PathInstruction],
mut start: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>, mut start: Vec<(Vec<PathInstruction>, Pattern<'a>)>,
end: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>, end: Vec<(Vec<PathInstruction>, Pattern<'a>)>,
branch: &Branch<'a>, branch: &Branch<'a>,
guard: Guard<'a>,
pattern: Pattern<'a>, pattern: Pattern<'a>,
) -> Option<Branch<'a>> { ) -> Option<Branch<'a>> {
use Pattern::*; use Pattern::*;
@ -590,13 +660,14 @@ fn to_relevant_branch_help<'a>(
tag_id: *tag_id, tag_id: *tag_id,
}); });
(new_path, Guard::NoGuard, pattern) (new_path, pattern)
}); });
start.extend(sub_positions); start.extend(sub_positions);
start.extend(end); start.extend(end);
Some(Branch { Some(Branch {
goal: branch.goal, goal: branch.goal,
guard: branch.guard.clone(),
patterns: start, patterns: start,
}) })
} }
@ -626,13 +697,14 @@ fn to_relevant_branch_help<'a>(
index: index as u64, index: index as u64,
tag_id, tag_id,
}); });
(new_path, Guard::NoGuard, pattern) (new_path, pattern)
}); });
start.extend(sub_positions); start.extend(sub_positions);
start.extend(end); start.extend(end);
Some(Branch { Some(Branch {
goal: branch.goal, goal: branch.goal,
guard: branch.guard.clone(),
patterns: start, patterns: start,
}) })
} }
@ -665,7 +737,7 @@ fn to_relevant_branch_help<'a>(
{ {
// NOTE here elm unboxes, but we ignore that // NOTE here elm unboxes, but we ignore that
// Path::Unbox(Box::new(path.clone())) // Path::Unbox(Box::new(path.clone()))
start.push((path.to_vec(), guard, arg.0)); start.push((path.to_vec(), arg.0));
start.extend(end); start.extend(end);
} }
} }
@ -680,7 +752,7 @@ fn to_relevant_branch_help<'a>(
index: index as u64, index: index as u64,
tag_id, tag_id,
}); });
(new_path, Guard::NoGuard, pattern) (new_path, pattern)
}); });
start.extend(sub_positions); start.extend(sub_positions);
start.extend(end); start.extend(end);
@ -699,7 +771,7 @@ fn to_relevant_branch_help<'a>(
index: index as u64, index: index as u64,
tag_id, tag_id,
}); });
(new_path, Guard::NoGuard, pattern) (new_path, pattern)
}); });
start.extend(sub_positions); start.extend(sub_positions);
start.extend(end); start.extend(end);
@ -708,6 +780,7 @@ fn to_relevant_branch_help<'a>(
Some(Branch { Some(Branch {
goal: branch.goal, goal: branch.goal,
guard: branch.guard.clone(),
patterns: start, patterns: start,
}) })
} }
@ -719,6 +792,7 @@ fn to_relevant_branch_help<'a>(
start.extend(end); start.extend(end);
Some(Branch { Some(Branch {
goal: branch.goal, goal: branch.goal,
guard: branch.guard.clone(),
patterns: start, patterns: start,
}) })
} }
@ -730,6 +804,7 @@ fn to_relevant_branch_help<'a>(
start.extend(end); start.extend(end);
Some(Branch { Some(Branch {
goal: branch.goal, goal: branch.goal,
guard: branch.guard.clone(),
patterns: start, patterns: start,
}) })
} }
@ -741,6 +816,7 @@ fn to_relevant_branch_help<'a>(
start.extend(end); start.extend(end);
Some(Branch { Some(Branch {
goal: branch.goal, goal: branch.goal,
guard: branch.guard.clone(),
patterns: start, patterns: start,
}) })
} }
@ -752,6 +828,7 @@ fn to_relevant_branch_help<'a>(
start.extend(end); start.extend(end);
Some(Branch { Some(Branch {
goal: branch.goal, goal: branch.goal,
guard: branch.guard.clone(),
patterns: start, patterns: start,
}) })
} }
@ -765,6 +842,7 @@ fn to_relevant_branch_help<'a>(
start.extend(end); start.extend(end);
Some(Branch { Some(Branch {
goal: branch.goal, goal: branch.goal,
guard: branch.guard.clone(),
patterns: start, patterns: start,
}) })
} }
@ -777,15 +855,15 @@ fn to_relevant_branch_help<'a>(
enum Extract<'a> { enum Extract<'a> {
NotFound, NotFound,
Found { Found {
start: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>, start: Vec<(Vec<PathInstruction>, Pattern<'a>)>,
found_pattern: (Guard<'a>, Pattern<'a>), found_pattern: Pattern<'a>,
end: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>, end: Vec<(Vec<PathInstruction>, Pattern<'a>)>,
}, },
} }
fn extract<'a>( fn extract<'a>(
selected_path: &[PathInstruction], selected_path: &[PathInstruction],
path_patterns: Vec<(Vec<PathInstruction>, Guard<'a>, Pattern<'a>)>, path_patterns: Vec<(Vec<PathInstruction>, Pattern<'a>)>,
) -> Extract<'a> { ) -> Extract<'a> {
let mut start = Vec::new(); let mut start = Vec::new();
@ -795,7 +873,7 @@ fn extract<'a>(
if current.0 == selected_path { if current.0 == selected_path {
return Extract::Found { return Extract::Found {
start, start,
found_pattern: (current.1, current.2), found_pattern: current.1,
end: it.collect::<Vec<_>>(), end: it.collect::<Vec<_>>(),
}; };
} else { } else {
@ -812,10 +890,10 @@ fn is_irrelevant_to<'a>(selected_path: &[PathInstruction], branch: &Branch<'a>)
match branch match branch
.patterns .patterns
.iter() .iter()
.find(|(path, _, _)| path == selected_path) .find(|(path, _)| path == selected_path)
{ {
None => true, None => true,
Some((_, guard, pattern)) => guard.is_none() && !needs_tests(pattern), Some((_, pattern)) => branch.guard.is_none() && !needs_tests(pattern),
} }
} }
@ -843,8 +921,10 @@ fn pick_path<'a>(branches: &'a [Branch]) -> &'a Vec<PathInstruction> {
// is choice path // is choice path
for branch in branches { for branch in branches {
for (path, guard, pattern) in &branch.patterns { for (path, pattern) in &branch.patterns {
if !guard.is_none() || needs_tests(&pattern) { // NOTE we no longer check for the guard here
// if !branch.guard.is_none() || needs_tests(&pattern) {
if needs_tests(pattern) {
all_paths.push(path); all_paths.push(path);
} else { } else {
// do nothing // do nothing
@ -916,7 +996,7 @@ where
let mut min_paths = vec![first_path]; let mut min_paths = vec![first_path];
for path in all_paths { for path in all_paths {
let weight = small_defaults(branches, &path); let weight = small_defaults(branches, path);
use std::cmp::Ordering; use std::cmp::Ordering;
match weight.cmp(&min_weight) { match weight.cmp(&min_weight) {
@ -960,6 +1040,7 @@ enum Decider<'a, T> {
/// after assigning to symbol, the stmt jumps to this label /// after assigning to symbol, the stmt jumps to this label
id: JoinPointId, id: JoinPointId,
stmt: Stmt<'a>, stmt: Stmt<'a>,
pattern: Pattern<'a>,
success: Box<Decider<'a, T>>, success: Box<Decider<'a, T>>,
failure: Box<Decider<'a, T>>, failure: Box<Decider<'a, T>>,
@ -997,11 +1078,15 @@ pub fn optimize_when<'a>(
.into_iter() .into_iter()
.enumerate() .enumerate()
.map(|(index, (pattern, guard, branch))| { .map(|(index, (pattern, guard, branch))| {
((guard, pattern, index as u64), (index as u64, branch)) let has_guard = !guard.is_none();
(
(guard, pattern.clone(), index as u64),
(index as u64, branch, pattern, has_guard),
)
}) })
.unzip(); .unzip();
let indexed_branches: Vec<(u64, Stmt<'a>)> = _indexed_branches; let indexed_branches: Vec<_> = _indexed_branches;
let decision_tree = compile(patterns); let decision_tree = compile(patterns);
let decider = tree_to_decider(decision_tree); let decider = tree_to_decider(decision_tree);
@ -1013,7 +1098,14 @@ pub fn optimize_when<'a>(
let mut choices = MutMap::default(); let mut choices = MutMap::default();
let mut jumps = Vec::new(); let mut jumps = Vec::new();
for (index, branch) in indexed_branches.into_iter() { for (index, mut branch, pattern, has_guard) in indexed_branches.into_iter() {
// bind the fields referenced in the pattern. For guards this happens separately, so
// the pattern variables are defined when evaluating the guard.
if !has_guard {
branch =
crate::ir::store_pattern(env, procs, layout_cache, &pattern, cond_symbol, branch);
}
let ((branch_index, choice), opt_jump) = create_choices(&target_counts, index, branch); let ((branch_index, choice), opt_jump) = create_choices(&target_counts, index, branch);
if let Some((index, body)) = opt_jump { if let Some((index, body)) = opt_jump {
@ -1125,15 +1217,9 @@ fn test_to_equality<'a>(
cond_layout: &Layout<'a>, cond_layout: &Layout<'a>,
path: &[PathInstruction], path: &[PathInstruction],
test: Test<'a>, test: Test<'a>,
) -> ( ) -> (StoresVec<'a>, Symbol, Symbol, Option<ConstructorKnown<'a>>) {
StoresVec<'a>,
Symbol,
Symbol,
Layout<'a>,
Option<ConstructorKnown<'a>>,
) {
let (rhs_symbol, mut stores, test_layout) = let (rhs_symbol, mut stores, test_layout) =
path_to_expr_help(env, cond_symbol, &path, *cond_layout); path_to_expr_help(env, cond_symbol, path, *cond_layout);
match test { match test {
Test::IsCtor { tag_id, union, .. } => { Test::IsCtor { tag_id, union, .. } => {
@ -1163,7 +1249,6 @@ fn test_to_equality<'a>(
stores, stores,
lhs_symbol, lhs_symbol,
rhs_symbol, rhs_symbol,
Layout::Builtin(Builtin::Int64),
Some(ConstructorKnown::OnlyPass { Some(ConstructorKnown::OnlyPass {
scrutinee: path_symbol, scrutinee: path_symbol,
layout: *cond_layout, layout: *cond_layout,
@ -1181,13 +1266,7 @@ fn test_to_equality<'a>(
let lhs_symbol = env.unique_symbol(); let lhs_symbol = env.unique_symbol();
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int64), lhs)); stores.push((lhs_symbol, Layout::Builtin(Builtin::Int64), lhs));
( (stores, lhs_symbol, rhs_symbol, None)
stores,
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Int64),
None,
)
} }
Test::IsFloat(test_int) => { Test::IsFloat(test_int) => {
@ -1197,13 +1276,7 @@ fn test_to_equality<'a>(
let lhs_symbol = env.unique_symbol(); let lhs_symbol = env.unique_symbol();
stores.push((lhs_symbol, Layout::Builtin(Builtin::Float64), lhs)); stores.push((lhs_symbol, Layout::Builtin(Builtin::Float64), lhs));
( (stores, lhs_symbol, rhs_symbol, None)
stores,
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Float64),
None,
)
} }
Test::IsByte { Test::IsByte {
@ -1213,13 +1286,7 @@ fn test_to_equality<'a>(
let lhs_symbol = env.unique_symbol(); let lhs_symbol = env.unique_symbol();
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int8), lhs)); stores.push((lhs_symbol, Layout::Builtin(Builtin::Int8), lhs));
( (stores, lhs_symbol, rhs_symbol, None)
stores,
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Int8),
None,
)
} }
Test::IsBit(test_bit) => { Test::IsBit(test_bit) => {
@ -1227,13 +1294,7 @@ fn test_to_equality<'a>(
let lhs_symbol = env.unique_symbol(); let lhs_symbol = env.unique_symbol();
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int1), lhs)); stores.push((lhs_symbol, Layout::Builtin(Builtin::Int1), lhs));
( (stores, lhs_symbol, rhs_symbol, None)
stores,
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Int1),
None,
)
} }
Test::IsStr(test_str) => { Test::IsStr(test_str) => {
@ -1242,13 +1303,7 @@ fn test_to_equality<'a>(
stores.push((lhs_symbol, Layout::Builtin(Builtin::Str), lhs)); stores.push((lhs_symbol, Layout::Builtin(Builtin::Str), lhs));
( (stores, lhs_symbol, rhs_symbol, None)
stores,
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Str),
None,
)
} }
} }
} }
@ -1257,7 +1312,6 @@ type Tests<'a> = std::vec::Vec<(
bumpalo::collections::Vec<'a, (Symbol, Layout<'a>, Expr<'a>)>, bumpalo::collections::Vec<'a, (Symbol, Layout<'a>, Expr<'a>)>,
Symbol, Symbol,
Symbol, Symbol,
Layout<'a>,
Option<ConstructorKnown<'a>>, Option<ConstructorKnown<'a>>,
)>; )>;
@ -1271,13 +1325,7 @@ fn stores_and_condition<'a>(
// Assumption: there is at most 1 guard, and it is the outer layer. // Assumption: there is at most 1 guard, and it is the outer layer.
for (path, test) in test_chain { for (path, test) in test_chain {
tests.push(test_to_equality( tests.push(test_to_equality(env, cond_symbol, cond_layout, &path, test))
env,
cond_symbol,
&cond_layout,
&path,
test,
))
} }
tests tests
@ -1403,7 +1451,7 @@ fn compile_tests<'a>(
fail: &'a Stmt<'a>, fail: &'a Stmt<'a>,
mut cond: Stmt<'a>, mut cond: Stmt<'a>,
) -> Stmt<'a> { ) -> Stmt<'a> {
for (new_stores, lhs, rhs, _layout, opt_constructor_info) in tests.into_iter() { for (new_stores, lhs, rhs, opt_constructor_info) in tests.into_iter() {
match opt_constructor_info { match opt_constructor_info {
None => { None => {
cond = compile_test(env, ret_layout, new_stores, lhs, rhs, fail, cond); cond = compile_test(env, ret_layout, new_stores, lhs, rhs, fail, cond);
@ -1486,7 +1534,7 @@ fn decide_to_branching<'a>(
match decider { match decider {
Leaf(Jump(label)) => { Leaf(Jump(label)) => {
let index = jumps let index = jumps
.binary_search_by_key(&label, |ref r| r.0) .binary_search_by_key(&label, |r| r.0)
.expect("jump not in list of jumps"); .expect("jump not in list of jumps");
Stmt::Jump(jumps[index].1, &[]) Stmt::Jump(jumps[index].1, &[])
@ -1495,6 +1543,7 @@ fn decide_to_branching<'a>(
Guarded { Guarded {
id, id,
stmt, stmt,
pattern,
success, success,
failure, failure,
} => { } => {
@ -1540,12 +1589,14 @@ fn decide_to_branching<'a>(
borrow: false, borrow: false,
}; };
Stmt::Join { let join = Stmt::Join {
id, id,
parameters: arena.alloc([param]), parameters: arena.alloc([param]),
remainder: arena.alloc(stmt), remainder: arena.alloc(stmt),
body: arena.alloc(decide), body: arena.alloc(decide),
} };
crate::ir::store_pattern(env, procs, layout_cache, &pattern, cond_symbol, join)
} }
Chain { Chain {
test_chain, test_chain,
@ -1589,7 +1640,7 @@ fn decide_to_branching<'a>(
if number_of_tests == 1 { if number_of_tests == 1 {
// if there is just one test, compile to a simple if-then-else // if there is just one test, compile to a simple if-then-else
let (new_stores, lhs, rhs, _layout, _cinfo) = tests.into_iter().next().unwrap(); let (new_stores, lhs, rhs, _cinfo) = tests.into_iter().next().unwrap();
compile_test_help( compile_test_help(
env, env,
@ -1835,7 +1886,7 @@ fn fanout_decider<'a>(
let fallback_decider = tree_to_decider(fallback); let fallback_decider = tree_to_decider(fallback);
let necessary_tests = edges let necessary_tests = edges
.into_iter() .into_iter()
.map(|(test, tree)| fanout_decider_help(tree, test, &fallback_decider)) .map(|(test, tree)| fanout_decider_help(tree, test))
.collect(); .collect();
Decider::FanOut { Decider::FanOut {
@ -1848,25 +1899,15 @@ fn fanout_decider<'a>(
fn fanout_decider_help<'a>( fn fanout_decider_help<'a>(
dectree: DecisionTree<'a>, dectree: DecisionTree<'a>,
guarded_test: GuardedTest<'a>, guarded_test: GuardedTest<'a>,
fallback_decider: &Decider<'a, u64>,
) -> (Test<'a>, Decider<'a, u64>) { ) -> (Test<'a>, Decider<'a, u64>) {
let decider = tree_to_decider(dectree);
match guarded_test { match guarded_test {
GuardedTest::TestGuarded { test, id, stmt } => { GuardedTest::Placeholder | GuardedTest::GuardedNoTest { .. } => {
let guarded = Decider::Guarded {
id,
stmt,
success: Box::new(decider),
failure: Box::new(fallback_decider.clone()),
};
(test, guarded)
}
GuardedTest::GuardedNoTest { .. } => {
unreachable!("this would not end up in a switch") unreachable!("this would not end up in a switch")
} }
GuardedTest::TestNotGuarded { test } => (test, decider), GuardedTest::TestNotGuarded { test } => {
let decider = tree_to_decider(dectree);
(test, decider)
}
} }
} }
@ -1877,30 +1918,14 @@ fn chain_decider<'a>(
success_tree: DecisionTree<'a>, success_tree: DecisionTree<'a>,
) -> Decider<'a, u64> { ) -> Decider<'a, u64> {
match guarded_test { match guarded_test {
GuardedTest::TestGuarded { test, id, stmt } => { GuardedTest::GuardedNoTest { id, stmt, pattern } => {
let failure = Box::new(tree_to_decider(failure_tree));
let success = Box::new(tree_to_decider(success_tree));
let guarded = Decider::Guarded {
id,
stmt,
success,
failure: failure.clone(),
};
Decider::Chain {
test_chain: vec![(path, test)],
success: Box::new(guarded),
failure,
}
}
GuardedTest::GuardedNoTest { id, stmt } => {
let failure = Box::new(tree_to_decider(failure_tree)); let failure = Box::new(tree_to_decider(failure_tree));
let success = Box::new(tree_to_decider(success_tree)); let success = Box::new(tree_to_decider(success_tree));
Decider::Guarded { Decider::Guarded {
id, id,
stmt, stmt,
pattern,
success, success,
failure: failure.clone(), failure: failure.clone(),
} }
@ -1912,6 +1937,11 @@ fn chain_decider<'a>(
to_chain(path, test, success_tree, failure_tree) to_chain(path, test, success_tree, failure_tree)
} }
} }
GuardedTest::Placeholder => {
// ?
tree_to_decider(success_tree)
}
} }
} }
@ -2022,11 +2052,13 @@ fn insert_choices<'a>(
Guarded { Guarded {
id, id,
stmt, stmt,
pattern,
success, success,
failure, failure,
} => Guarded { } => Guarded {
id, id,
stmt, stmt,
pattern,
success: Box::new(insert_choices(choice_dict, *success)), success: Box::new(insert_choices(choice_dict, *success)),
failure: Box::new(insert_choices(choice_dict, *failure)), failure: Box::new(insert_choices(choice_dict, *failure)),
}, },

View file

@ -353,7 +353,7 @@ fn to_nonredundant_rows(
vec![simplify(&loc_pat.value)] vec![simplify(&loc_pat.value)]
}; };
if is_useful(checked_rows.clone(), next_row.clone()) { if matches!(guard, Guard::HasGuard) || is_useful(checked_rows.clone(), next_row.clone()) {
checked_rows.push(next_row); checked_rows.push(next_row);
} else { } else {
return Err(Error::Redundant { return Err(Error::Redundant {

View file

@ -50,7 +50,7 @@ use roc_module::symbol::{IdentIds, ModuleId, Symbol};
// Let's work through the `Cons x xx` example // Let's work through the `Cons x xx` example
// //
// First we need to know the constructor of `xs` in the particular block. This information would // First we need to know the constructor of `xs` in the particular block. This information would
// normally be lost when we compile pattern matches, but we keep it in the `BrachInfo` field of // normally be lost when we compile pattern matches, but we keep it in the `BranchInfo` field of
// switch branches. here we also store the symbol that was switched on, and the layout of that // switch branches. here we also store the symbol that was switched on, and the layout of that
// symbol. // symbol.
// //
@ -160,18 +160,10 @@ impl<'a, 'i> Env<'a, 'i> {
fn try_insert_struct_info(&mut self, symbol: Symbol, layout: &Layout<'a>) { fn try_insert_struct_info(&mut self, symbol: Symbol, layout: &Layout<'a>) {
use Layout::*; use Layout::*;
match layout { if let Struct(fields) = layout {
Struct(fields) => {
self.constructor_map.insert(symbol, 0); self.constructor_map.insert(symbol, 0);
self.layout_map.insert(symbol, Layout::Struct(fields)); self.layout_map.insert(symbol, Layout::Struct(fields));
} }
Closure(_, lambda_set, _) => {
self.constructor_map.insert(symbol, 0);
self.layout_map
.insert(symbol, lambda_set.runtime_representation());
}
_ => {}
}
} }
fn insert_struct_info(&mut self, symbol: Symbol, fields: &'a [Layout<'a>]) { fn insert_struct_info(&mut self, symbol: Symbol, fields: &'a [Layout<'a>]) {
@ -187,7 +179,7 @@ impl<'a, 'i> Env<'a, 'i> {
pub fn unique_symbol(&mut self) -> Symbol { pub fn unique_symbol(&mut self) -> Symbol {
let ident_id = self.ident_ids.gen_unique(); let ident_id = self.ident_ids.gen_unique();
self.home.register_debug_idents(&self.ident_ids); self.home.register_debug_idents(self.ident_ids);
Symbol::new(self.home, ident_id) Symbol::new(self.home, ident_id)
} }
@ -195,7 +187,7 @@ impl<'a, 'i> Env<'a, 'i> {
fn manual_unique_symbol(home: ModuleId, ident_ids: &mut IdentIds) -> Symbol { fn manual_unique_symbol(home: ModuleId, ident_ids: &mut IdentIds) -> Symbol {
let ident_id = ident_ids.gen_unique(); let ident_id = ident_ids.gen_unique();
home.register_debug_idents(&ident_ids); home.register_debug_idents(ident_ids);
Symbol::new(home, ident_id) Symbol::new(home, ident_id)
} }
@ -244,10 +236,6 @@ fn layout_for_constructor<'a>(
debug_assert_eq!(constructor, 0); debug_assert_eq!(constructor, 0);
HasFields(fields) HasFields(fields)
} }
Closure(_arguments, _lambda_set, _result) => {
// HasFields(fields)
ConstructorLayout::Unknown
}
other => unreachable!("weird layout {:?}", other), other => unreachable!("weird layout {:?}", other),
} }
} }
@ -368,21 +356,11 @@ pub fn expand_and_cancel_proc<'a>(
let mut introduced = Vec::new_in(env.arena); let mut introduced = Vec::new_in(env.arena);
for (layout, symbol) in arguments { for (layout, symbol) in arguments {
match layout { if let Layout::Struct(fields) = layout {
Layout::Struct(fields) => {
env.insert_struct_info(*symbol, fields); env.insert_struct_info(*symbol, fields);
introduced.push(*symbol); introduced.push(*symbol);
} }
Layout::Closure(_arguments, _lambda_set, _result) => {
// TODO can this be improved again?
// let fpointer = Layout::FunctionPointer(arguments, result);
// let fields = env.arena.alloc([fpointer, *closure_layout.layout]);
// env.insert_struct_info(*symbol, fields);
// introduced.push(*symbol);
}
_ => {}
}
} }
let result = expand_and_cancel(env, stmt); let result = expand_and_cancel(env, stmt);
@ -585,29 +563,6 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
expand_and_cancel(env, cont) expand_and_cancel(env, cont)
} }
Invoke {
symbol,
call,
layout,
pass,
fail,
exception_id,
} => {
let pass = expand_and_cancel(env, pass);
let fail = expand_and_cancel(env, fail);
let stmt = Invoke {
symbol: *symbol,
call: call.clone(),
layout: *layout,
pass,
fail,
exception_id: *exception_id,
};
env.arena.alloc(stmt)
}
Join { Join {
id, id,
parameters, parameters,
@ -627,7 +582,7 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
env.arena.alloc(stmt) env.arena.alloc(stmt)
} }
Resume(_) | Ret(_) | Jump(_, _) | RuntimeError(_) => stmt, Ret(_) | Jump(_, _) | RuntimeError(_) => stmt,
} }
}; };

Some files were not shown because too many files have changed in this diff Show more